LoginSignup
0
0

More than 5 years have passed since last update.

Shellの基本を学ぶ(5)制御構文

Posted at

while

test が true の間実行

while test; do
  ;; some code
done

until

test が false の間実行

while test: do
  ;; some code
done

for

それぞれのワードがVARに入る。ワードのクオートは不要

for VAR in WORDS; do
  ;; some code
done

例えば次のようなコマンドの実行。lsの実行結果を一つづつループで回している。

$ for i in $(ls); do echo $i; done
answer
bin
hello_world.sh
hw
hw.h
logfile
ls.txt
output.sh
tn.sh
variables

ちなみに、ダブルクオートで変数をくくると1つの変数として見なされる。

$ for i in hello world tsuyoshi; do echo $i; done
hello
world
tsuyoshi
$ for i in "hello world tsuyoshi"; do echo $i; done
hello world tsuyoshi

Sample

サンプルは、引数を指定すると、該当の拡張子の前に第3引数の拡張子を挿入してくれるプログラム。
ちなみに、echo mv ...のパートはデバッグテクニックらしく、これで一旦実行して、実際に思った通りだったら、echoを外して実行する。特にforが該当の拡張子だけで回るのはカッコいい。

forsample

#!/bin/bash

if [[ $# -ne 2 ]]; then
  echo "Two parameters requires"
fi

for f in *"$1"; do
  base=$(basename "$f" "$1")
  echo mv "$f" "${base}$2.$1"
done

実行

実行時のtouch の使い方もカッコいい

$ touch file{1..10}.txt
$ touch image{1..5}.img
$ ls
file1.txt   file3.txt   file6.txt   file9.txt   image3.img
file10.txt  file4.txt   file7.txt   image1.img  image4.img
file2.txt   file5.txt   file8.txt   image2.img  image5.img
$ ./forsample txt bak
mv file1.txt file1.bak.txt
mv file10.txt file10.bak.txt
mv file2.txt file2.bak.txt
mv file3.txt file3.bak.txt
mv file4.txt file4.bak.txt
mv file5.txt file5.bak.txt
mv file6.txt file6.bak.txt
mv file7.txt file7.bak.txt
mv file8.txt file8.bak.txt
mv file9.txt file9.bak.txt

C-Style Loop

for (( INIT; TEST; UPDATE )); do
  ;; some code
done

counter

#!/bin/bash

for (( i=0; i<10; ++i )); do
  echo "number ${i}"
done

実行

$ ./counter
number 0
number 1
number 2
number 3
number 4
number 5
number 6
number 7
number 8
number 9

ワンライナーでやろうと頑張ってみてうまくいかなかったが、do の後に;をつけていたのを削除するとうまくいった。

$ for (( i=0; i<10; ++i )); do echo "number ${i}"; done
number 0
number 1
number 2
number 3
number 4
number 5
number 6
number 7
number 8
number 9

drawline

サンプルが面白かったので、そのまま写経してみた。

#!/bin/bash

if [[ ! $1 ]]; then
  echo "need line length argument" >&2
  exit 1
fi

if [[ $1 =~ ^[0-9]+$ ]]; then
  length="$1"
else
  echo "Length has to be a number" >&2
  exit 2
fi

char="="
if [[ $2 ]]; then
  char="$2"
fi

line=
for (( i=0; i<length; ++i )); do
  line="${line}${char}"
done

printf "%s\n" "$line"
exit 0

実行

$ ./drawline 10
==========
$ ./drawline 10 --
--------------------

ここでポイントは、最後の printf "%s\n" "$line" でこれが printf "$line\n" だと Substitution なので、最初の文字が-の場合、オプションと認識されてうまくいかない。フォーマットを使うことで回避できている。

Break and Continue

  • break: ループをやめる
  • continue: 現在のイテレーションをスキップして、次から始める

他の言語と同じ。for, while, until で使える。次のサンプルは、ファイルの中をサーチして、該当の行が来たら表示するもの。

mysearch

#!/bin/bash

if [[ ! $1 ]]; then
  echo "First argument missing" >$2
  exit 1
fi 

flag=
while read -r; do
  if [[ $REPLY =~ $1 ]]; then
    echo "$1 found: $REPLY"
    flag="done"
    break
  fi 
done

if [[ ! $flag ]]; then
  echo "$1 not found."
fi 

exit 0

data.txt

This is the text.
You need to find a text.
It might be foo.
Also might be bar.
But it is crosstrek.
Keep on loving Subaru.
See you soon.

Regards,
Tsuyoshi

実行

$ ./mysearch crosstrek < ./data.txt
crosstrek found: But it is crosstrek.
$ ./mysearch wrx < ./data.txt
wrx not found.

例によって元のサンプルにセンスを感じたので写経。$REPLY の使い方が渋い

他の Pre-defined の変数はこちら。9.1. Internal Variables

デフォルトの変数を使うことで、シンプルに感じる。

#!/bin/bash

if [[ ! $1 ]]; then
  echo "First argument missing" >&2
  exit 1
fi 

found=""

while read -r; do 
  if [[ ! $found ]]; then
    if [[ $REPLY =~ $1 ]]; then
      found="yes"
    else 
      continue
    fi
  fi
  echo "$REPLY"
done

exit 0

実行

最後の行が表示されないが、それはもうすぐわかるらしい。

$ ./stripto crosstrek < data.txt
But it is crosstrek.
Keep on loving Subaru.
See you soon.

Regards,

case


case WORD in
  PATTERN1)
     some code;;
  PATTERN2)
     some code;;
    :
esac

他の言語と同じ

Sample

animals

#!/bin/bash

case $1 in
  tiger)
    echo "Super cool!";;
  carp)
    echo "So so";;
  giant)
    echo "Boo!.";;
  *)
    echo "I don't think about it";;
esac

実行

$ ./animals tiger
Super cool!
$ ./animals giant
Boo!.
$ ./animals star
I don't think about it

サンプル2

もうすこし複雑なパターンマッチも可能な様子

filetype

#!/bin/bash

case $1 in
  *.png|*.jpg|*.gif)
    echo "$1 is image";;
  *.cs|*.java|*.js|*.sh)
    echo "$1 is programming language source code";;
  *)
    echo "Don't ask me, I don't know";;
esac

exit 0

実行

$ ./filetype some.jpg
some.jpg is image
$ ./filetype Program.cs
Program.cs is programming language source code
$ ./filetype some.txt
Don't ask me, I don't know

Command Groups

{} で括られたコマンド。複数のコマンドを一つのステートメントに入れる。I/O のリダイレクトをグループに適用できる。if や while ループに適用できる。リターンステータスは、最後のコマンド実行の結果になる。

{cmd; cmd2; cmd3;} というスタイル。セミコロンで新しい行を表現する。ブレースの周りにはスペースが必要。セミコロンで終了すること。

|| and &&

論理演算ではない用途でも使う。コマンドの実行結果によって、後続のコマンドの実行を決める。

&&

先行したコマンド実行が成功した時のみ実行する。例えば次のはディレクトリ作成が成功した時のみ、そのディレクトリに移動する。

$ mkdir newdir && cd newdir

||

直前のコマンドが失敗した時のみ、次のコマンドを実行する。次のコマンドは、引数がない時のみ、エラーメッセージを表示する。

[[ $1 ]] || echo "missing argument">&2

例えば次のスクリプトは常に異常終了する。$1があったら、それはそれで正常終了だし、echoでエラーメッセージを表示しても、表示自体は成功するので、常に、exit 1になる。

[[ $1 ]] || echo "missing argument" >&2 && exit 1

こういう時には、コマンドグルーピングを使う

[[ $1 ]] || { echo "missing argument">&2; exit 1;}

サンプル

先ほどのサンプルを改造する。検索した文字列のある行以降を表示する。前は、最後の行が表示されない問題があった。その原因はwhile read -r; do で最後のEOFを判断しているが、最後の行がTsuyoshiEOFとなっている場合は、ここでtrueとなるため、最後の行が表示されない。read -f が false になった時のみ、[[ $REPLY ]] を評価する。もし、他に文字列が含まれていたらループを実行を続行する。

他には、コマンドグループを使って、文字列チェックを一業で記述している。

#!/bin/bash

[[ $1 ]] || { echo "First argument missing" >&2; exit 1; }

found=""

while read -r || [[ $REPLY ]]; do 
  if [[ ! $found ]]; then
    if [[ $REPLY =~ $1 ]]; then
      found="yes"
    else 
      continue
    fi
  fi
  echo "$REPLY"
done

exit 0

実行

ちゃんと最後の行が表示されている。

$ ./stripto crosstrek < data.txt
But it is crosstrek.
Keep on loving Subaru.
See you soon.

Regards,
Tsuyoshi
invincible:forloop ushio$ ./stripto
First argument missing
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0