#author("2019-02-02T11:36:51+00:00","default:haikikyou","haikikyou")
Bashマニュアルから(自分が)頻繁に使用するであろう機能をピックアップしてまとめる。


#contents

* コマンド [#j1138789]

** ループ [#da0f596d]

*** until [#r0605004]

 until test-commands; do consequent-commands; done

条件が成り立つまで繰り返す。

#geshi(bash){{{
# --- commands ---

rm "job.done"

{ sleep 3 && touch job.done; } &

retry=0
until [ -f "job.done" ]
do
  sleep 1
  ((retry+=1))
  if [ $retry -gt 5 ]; then
     echo "exceeded retry max:5."
     exit 1
  fi
done
echo "success"

# --- output ---

success
}}}

*** while [#ec71bf2b]

 while test-commands; do consequent-commands; done

条件が成り立つ間繰り返す。

#geshi(bash){{{
# --- commands ---

touch "job.lock"

{ sleep 3 && rm job.lock; } &

retry=0
while [ -f "job.lock" ]
do
  sleep 1
  ((retry+=1))
  if [ $retry -gt 5 ]; then
     echo "exceeded retry max:5."
     exit 1
  fi
done
echo "job done"

# --- output ---

job done
}}}


*** for [#k816bb81]

 for name [ [in [words …] ] ; ] do commands; done
 for (( expr1 ; expr2 ; expr3 )) ; do commands ; done

wordsの数繰り返す。&code(){(( ))}; は、他のプログラミング言語同様の構文。~
expr1が最初に評価され、expr2がゼロになるまで、commandsを繰り返し評価する。expr2がゼロでない場合は、expr3が評価される。

#geshi(bash){{{
# --- commands ---

for w in {localhost,192.168.3.4}; do
  echo "$w"
done

# --- output ---

localhost
192.168.3.4


# --- commands ---

for ((i=0; i < 5;i++)); do
  echo $i
done

# --- output ---

0
1
2
3
4
}}}


&label(warn){参考}; https://www.gnu.org/software/bash/manual/bash.html#Looping-Constructs
** 条件 [#hd3bad84]

** [#d0e8fd7a]

* 展開 [#vdd07017]

展開の種類には以下がある。展開の順序を知っておくことで、最終的にどのような結果が得られるのか迷わずに済むだろう。~
以下の順序で展開が行われる。

+ ブレース展開
 {a,b}  {x..y..incr}
+ チルダ展開 、[ プロセス置換 ]
 ~  ~-  ~+   [<(list)  or  >(list)]
+ パラメータと変数展開、算術式展開、コマンド置換
 ${param} etc
 $(( expression ))
 $(command) or  `command`
+  単語の分割
 word word     split by $IFS
+ パス名展開
 .  *  ?  etc

全ての展開後に、クオートの除去( \   '  "  )が行われる。

&label(warn){参考}; https://www.gnu.org/software/bash/manual/bash.html#Shell-Expansions

** ブレース展開 [#fc2c5271]
 {x..y[..incr]}
 
- {} を使って行なう。ブレース展開を使うと、値の組み合わせのパターン生成に便利な手法。
- 他の展開よりも先に行われるので、変数展開を期待して埋め込んでも意図した結果にならないだろう。
- { や , はバックスラッシュでエスケープできる。

#geshi(bash){{{
param=10                        #=> 

echo `seq 1 10`                 #=> 1 2 3 4 5 6 7 8 9 10
echo {1..10..1}                 #=> 1 2 3 4 5 6 7 8 9 10
echo {1..10..3}                 #=> 1 4 7 10
echo {1..10}                    #=> 1 2 3 4 5 6 7 8 9 10
echo {10..1..-1}                #=> 10 9 8 7 6 5 4 3 2 1
echo {10..-1}                   #=> 10 9 8 7 6 5 4 3 2 1 0 -1
echo {10..10..-1}               #=> 10
echo {1..$param}                #=> {1..10}
eval echo {1..$param}           #=> 1 2 3 4 5 6 7 8 9 10

echo test{1,}                   #=> test1 test
echo test{1,2,3}                #=> test1 test2 test3
echo test{,}                    #=> test test
echo test{{,}}                  #=> test{} test{}
echo {postgresql,pg_hba}.conf   #=> postgresql.conf pg_hba.conf
}}}

&label(warn){参考}; https://www.gnu.org/software/bash/manual/bash.html#Brace-Expansion

** チルダ展開 [#r8b69aa5]

~ や ~-、~+ といった文字列の展開である。

|&code(){~}; |$HOME|
|&code(){~<user>};|ユーザ<user>の$HOME|
|&code(){~+};|$PWD|
|&code(){~-};|${OLDPWD:'~-'}|
|&code(){~N、~+N};|dirs +N|
|&code(){~-N};|dirs -N|

実際の動きを以下例で確認してみる。

#geshi(bash){{{
pwd                             #=> /home/guest/workspace/bash

echo $PWD                       #=> /home/guest/workspace/bash
echo $OLDPWD                    #=> /home/guest/workspace

# ~, ~user
echo ~                          #=> /home/guest
echo ~guest                     #=> /home/guest
echo ~unknownuser               #=> ~unknownuser

# dirs
echo ~+                         #=> /home/guest/workspace/bash
echo ~-                         #=> /home/guest/workspace
unset OLDPWD                    #=> 
echo ~-                         #=> ~-

dirs -l                         #=> /home/guest/workspace/bash

pushd /tmp                      #=> /tmp ~/workspace/bash

echo ~1                         #=> /home/guest/workspace/bash
echo ~+1                        #=> /home/guest/workspace/bash
echo ~-1                        #=> /tmp
}}}


&label(warn){参考}; https://www.gnu.org/software/bash/manual/bash.html#Tilde-Expansion
** パラメータ展開 [#mf070ac4]

*** ${parameter} [#lc33dd13]
parameterの値に置換される。

#geshi(bash){{{
#!/usr/bin/env bash
PARAM="parameter"
echo $PARAM  #=> parameter
}}}

*** ${parameter:-word} [#r6cf08cd]

parameter が設定されていないか空文字列であれば、 wordを展開したものに置換される。そうでなければ、 parameter の値に置換される。

変数のデフォルト値を設定したい場合などによく使う。

#geshi(bash){{{
echo $USER #=> "moritetu"

# ${parameter:-word}
SSH_USER="${USER:-"guest"}"                                                                                                                                          
echo "$SSH_USER" #=> "moritetu"

SSH_OPTS="${OPTIONS:-""}"                                                                                                                                            
echo "$SSH_OPTS"  #=> ""
}}}

*** ${parameter:=word} [#h57e3e86]

parameterが設定されていないか空文字列であれば、 wordを展開したものがparameter に代入される。その後、parameter の値への置換が行われる。

#geshi(bash){{{
# ${parameter:=word}

# ex1
${COMMAND:="date"} #=> 2019年 1月30日 水曜日 22時10分22秒 JST

# ex2
: ${PARAM:="param1"}
echo $PARAM #=> param1
}}}

*** ${parameter:?word} [#v638fa90]

- parameterが空文字列または設定されていない場合、word を展開したものが標準エラー出力に出力される。
- wordがなければ パラメータが空文字列または設定されていないことを示すメッセージが標準エラー出力に出力される。
- 対話的シェルでなければ、 シェルは終了する。
- parameterに空文字列以外が設定されていれば、 parameter 値への置換が行われる。

#geshi(bash){{{
# ${parameter:?word}

# ex1
${not_defined_param:?} #=> not_defined_param: パラメータが null または設定されていません
# シェルは終了する

# ex2
${not_defined_param2:?"parameter is not defined"} #=> not_defined_param2: parameter is not defined
# シェルは終了する

# ex3
defined_param2="defined_param2"
echo ${defined_param2:?"parameter is not defined"} #=> defined_param2
}}}


*** ${parameter:+word} [#g28240aa]

parameter が空文字列または設定されていなければ、空文字列に置換される。そうでなければ word を展開したものに置換される。${parameter:-word} の逆。

#geshi(bash){{{
# ${parameter:+word}

echo ${parameter:+"word"} #=> ""

parameter="defined"
echo ${parameter:+"word"} #=> word
}}}
*** ${parameter:offset}、${parameter:offset:length} [#v6387b01]

- 部分文字列の展開を行なう。
- length指定がある場合は、parameter を展開したものから最大 length 文字を取り出す。length指定がない場合は、指定したインデックスから末尾までの文字を取り出す。
- 配列の場合は、指定したインデックスからlengthで指定される要素を取り出す。

#geshi(bash){{{
# ${parameter:offset}
parameter="012345"              #=> 
array=(0 1 2 3 4 5)             #=> 

echo ${parameter:2}             #=> 2345
echo ${parameter:-1}            #=> 012345
echo ${parameter:-2}            #=> 012345
echo ${array[@]:-2}             #=> 0 1 2 3 4 5
echo ${array[@]:2}              #=> 2 3 4 5
echo ${parameter:2:2}           #=> 23
echo ${parameter:2:-1}          #=> 234
echo ${array[@]:2:1}            #=> 2
echo ${array[@]:2:-1}           #=> parameter.sh: 行 8: -1: substring expression < 0
}}}

*** ${!prefix*}、${!prefix@} [#jd3445b4]

prefix で始まる全ての変数の名前に展開して、 IFS 特殊変数の最初の文字によって区切る。

#geshi(bash){{{
# ${!prefix*}
parameter_1="p1"                #=> 
parameter_2="p2"                #=> 
echo ${!param*}                 #=> parameter_1 parameter_2

(IFS="_|" ; echo ${!param*})    #=> parameter 1 parameter 2

# ${!prefix@}
parameter_1="p1"                #=> 
parameter_2="p2"                #=> 
echo ${!param@}                 #=> parameter_1 parameter_2

for p in "${!param@}"; do eval echo \$$p; done  #=> p1
p2
}}}

*** ${!name[*]}、${!name[@]} [#vf3ba1d3]

- 配列のキーのリストに展開される。ハッシュの場合は、キーを取り出すことができる。
- 配列の場合は、インデックスとなる。*と@の違いは、ダブルクォートで囲まれた場合の展開方法が異なる。

#geshi(bash){{{
array=(a b c d e f)             #=> 
# ${!name[*]}
echo ${!array[*]}               #=> 0 1 2 3 4 5

# ${!name[@]}
echo ${!array[@]}               #=> 0 1 2 3 4 5

# ハッシュ定義
declare -g -A map               #=> 
map=(["a"]=0 ["b"]=1 ["c"]=2 ["d"]=3 ["e"]=4 ["f"]=5)  #=> 
echo ${!map[*]}                 #=> a b c d e f
echo ${map[@]}                  #=> 0 1 2 3 4 5
echo ${!map[@]}                 #=> a b c d e f
}}}
*** ${#parameter} [#h4f5448b]

パラメータの長さを示す。文字列の場合は文字列の長さ、配列の場合は要素数。

#geshi(bash){{{
# ${$parameter}
strings="12345"                 #=> 
echo ${#strings}                #=> 5
strings="あいう"                 #=> 
echo ${#strings}                #=> 3

array=(0 1 2 3 4 5)             #=> 
echo ${#array}                  #=> 1
echo ${#array[@]}               #=> 6
}}}

*** ${parameter#word}、${parameter##word} [#y841fdc6]

- parameterに対しwordで前方一致するパターンを取り除いた結果を返す。#は最短一致、##は最長一致のパターン。
- parameterが@や*の配列変数の場合、全ての要素に対して順番に適用される。


#geshi(bash){{{
parameter="test.sh.j2"          #=> 
array=(index.html.erb hello.html.erb)  #=> 

# ${parameter#word}
echo ${parameter#*.}            #=> sh.j2
echo ${array[@]#*.}             #=> html.erb html.erb

# ${parameter##word}
echo ${parameter##*.}           #=> j2
echo ${array[@]##*.}            #=> erb erb
}}}

*** ${parameter%word}、${parameter%%word} [#tf4bc178]

- parameterに対しwordで''後方一致''するパターンを取り除いた結果を返す。#は最短一致、##は最長一致のパターン。
- parameterが@や*の配列変数の場合、全ての要素に対して順番に適用される。


#geshi(bash){{{
parameter="test.sh.j2"          #=> 
array=(index.html.erb hello.html.erb)  #=> 

# ${parameter%word}
echo ${parameter%.*}            #=> test.sh
echo ${array[@]%.*}             #=> index.html hello.html

# ${parameter%%word}
echo ${parameter%%.*}           #=> test
echo ${array[@]%%.*}            #=> index hello
}}}

*** ${parameter/pattern/string} [#i4603bba]

- parameterのpatternの最長一致する部分をstringに置換する。
- / で始まる場合は、patternにマッチした全てが置換される。
- # で始まる場合は、parameterを展開した値の先頭にマッチ。
- %で始まる場合は、parameterを展開した値の末尾にマッチ。
- parameterが、@や*の場合は、全ての要素に適用される。

#geshi(bash){{{
parameter="# this is a comment line."  #=> 
array=("# this is a comment line." "# this is not a comment line.")  #=> 

# ${parameter/pattern/string}
echo ${parameter/i/I}           #=> # thIs is a comment line.
echo ${parameter//i/I}          #=> # thIs Is a comment lIne.
echo ${parameter[*]//i/I}       #=> # thIs Is a comment lIne.

echo ${parameter/##/#=>}        #=> #=> this is a comment line.
echo ${parameter/#this/This}    #=> # this is a comment line.
echo ${parameter[@]/#this/This}  #=> # this is a comment line.

echo ${parameter/%./!}          #=> # this is a comment line!
echo ${parameter/%e/!}          #=> # this is a comment line.
echo ${parameter[@]/%e/!}       #=> # this is a comment line.
}}}

*** ${parameter^pattern}、${parameter^^pattern}、${parameter,pattern}、${parameter,,pattern} [#lce334bc]

- parameterに含まれるアルファベットの大文字小文字を変換する。
- ^ は、patternにマッチした小文字を大文字に変換し、, は、大文字を小文字に変換する。これらは、最初にマッチした部分のみ置換する。
- ^^ と ,, は、マッチした全ての文字を置換する。
- parameterが、@や* の場合は、全ての要素に適用される。

#geshi(bash){{{
parameter="hellO, WOrld"        #=> 
array=("hellO, WOrld" "hellO, WOrld2")  #=> 

# ${parameter^pattern}
echo ${parameter^l}             #=> hellO, WOrld
echo ${array[@]^l}              #=> hellO, WOrld hellO, WOrld2

# ${parameter^^pattern}
echo ${parameter^^l}            #=> heLLO, WOrLd
echo ${array[@]^^l}             #=> heLLO, WOrLd heLLO, WOrLd2

# ${parameter,pattern}
echo ${parameter,O}             #=> hellO, WOrld
echo ${array[@],,O}             #=> hello, World hello, World2

# ${parameter,,pattern}
echo ${parameter,,O}            #=> hello, World
echo ${array[@],,O}             #=> hello, World hello, World2
}}}


&label(warn){参考}; https://www.gnu.org/software/bash/manual/bash.html#Shell-Parameter-Expansion
* プロセス置換 [#we760055]

- &code(){>(list)};は、ファイルへの出力がlistへの入力となる。
- &code(){<(list)};は、listの出力がファイルへの入力となる。

&label(sample){サンプル}; ''lsの結果で*.shファイルのみを出力''

#geshi(bash,number){{{
a=
while IFS= read -r line
do
  a="$line:$a"
done < <(ls . | grep -e "\.sh$")
}}}

&label(sample){サンプル}; ''a.txtとb.txtをdiffの入力ファイルとして比較''

#geshi(bash,number){{{
diff -u <(cat a.txt) <(cat b.txt)
}}}

&label(sample){サンプル}; 

#geshi(bash,number){{{
exec 3>&1
exec > >(while read line; do echo "$(date): $line";done)

echo "hello"
echo "bar"

exec 1>&3 3>&-
}}}
* execを使った入出力のリダイレクト [#s12f0ef4]

execコマンドでリダイレクトだけを指定すると、シェル自身のファイル記述子とデバイスの対応を変更することができる。

****  &label(sample){例}; execを使った入力のリダイレクト [#z15e5164]
#geshi(bash,number){{{
# copy stdin to descriptor 3
exec 3<&0

# redirect stdin to test.c
exec < test.c

# read a line from test.c
read line
echo $line

# restore stdin and close descriptor 3
exec 0<&3 3<&-
}}}

**** &label(sample){例}; execを使った出力のリダイレクト [#z15e5164]

#geshi(bash,number){{{
# save stdout as descriptor 3
exec 3>&1

exec > test.txt

echo "this message is written into the test.txt"

# restore stdout and close descriptor 3
exec 1>&3 3>&-
}}}

特定の式や文の出力をまとめてリダイレクトしたい場合は、ブレースを使った複合コマンドでも処理できる。

&label(sample){例}; ''複合コマンドの出力をファイルにリダイレクトする''

#geshi(bash,number){{{
#!/usr/bin/env bash

# 複合コマンド
{
  # stdout.txtに出力
  echo "this message is written into stdout.txt"
  # stderr.txtに出力
  echo "this message is written into stderr.txt" >&2
} > stdout.txt 2> stderr.txt
}}}
* 参考リンク [#q8d32fb3]

- [[Advanced Bash-Scripting Guid>http://tldp.org/LDP/abs/html/index.html]] - &size(11){&color(gray){on http://tldp.org/LDP/abs/html/index.html};};
- [[GNU Bash manual>https://www.gnu.org/software/bash/manual/]] - &size(11){&color(gray){on https://www.gnu.org/software/bash/manual/};};
- [[Man page of BASH>https://linuxjm.osdn.jp/html/GNU_bash/man1/bash.1.html]] - &size(11){&color(gray){on https://linuxjm.osdn.jp/html/GNU_bash/man1/bash.1.html};};
- テストフレームワーク
-- [[Bats>https://github.com/sstephenson/bats]] - &size(11){&color(gray){on https://github.com/sstephenson/bats};};
-- [[shunit2>https://github.com/kward/shunit2]] - &size(11){&color(gray){on https://github.com/kward/shunit2};};
--- [[Pac Learner shUnit2 2.1.x ドキュメント>https://sites.google.com/site/paclearner/shunit2-documentation]] - &size(11){&color(gray){on https://sites.google.com/site/paclearner/shunit2-documentation};};
-- [[Baut (Bash Unittest Tool)>https://github.com/moritetu/baut]] - - &size(11){&color(gray){on https://github.com/moritetu/baut};};

トップ   一覧 単語検索 最終更新   ヘルプ   最終更新のRSS
目次
ダブルクリックで閉じるTOP | 閉じる
GO TO TOP