Bashマニュアルから(自分が)頻繁に使用するであろう機能をピックアップしてまとめる。

コマンド

ループ

until

until test-commands; do consequent-commands; done

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

# --- 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

while test-commands; do consequent-commands; done

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

# --- 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

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

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

# --- 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

参考 https://www.gnu.org/software/bash/manual/bash.html#Looping-Constructs

条件

if

if test-commands; then
  consequent-commands;
[elif more-test-commands; then
  more-consequents;]
[else alternate-consequents;]
fi

ifの条件式には、コマンドやBashの条件式(-aや-fなど)、算術式などを指定することができる。

# --- commands ---


# Arithmetic expression
(( 0 )); echo $?
(( 1 )); echo $?

#1
ls job.done
if ( cd . && test -f job.done ); then
    echo "job.done found"
fi

#2
i=0
(( i >= 0 )) ; echo $?
if (( i >= 0 )); then
  echo "i is more than 0"
fi

#3
i=0
(( (i >= 0) && (i < 1) )) ; echo $?
!(( (i >= 0) && (i < 1) )) ; echo $?
if !(( (i >= 0) && (i < 1) )); then
  echo "i is not (i >= 0) && (i < 1)"
else
  echo "i is (i >= 0) && (i < 1)"
fi

#4
log="FATAL: unexpected error"
if [[ "$log" =~ ^FATAL ]]; then
  echo "Error detected!"
fi

# --- output ---


# Arithmetic expression
1
0

#1
job.done
job.done found

#2
0
i is more than 0

#3
0
1
i is (i >= 0) && (i < 1)

#4
Error detected!

case

case word in
    [ [(] pattern [| pattern]) command-list ;;]esac
# --- commands ---

do_case() {
  echo "cond: $cond"
  case "$cond" in
    start)
      echo "match start"
      ;;
    s*p)
      echo "match s*p"
      ;;
    {a,b,c})
      echo "match {a,b,c}"
      ;;
    [0-5]*|[6-9]+)
      echo "match [0-5]*|[6-9]+"
      ;;
    (~)
      echo "match ~"
      ;&
    (fallback)
      echo "match fallback"
      ;;
    ($USER)
      echo "match \$USER"
      ;;&
    (/$USER)
      echo "match /\$USER"
      ;;
    ($USER*)
      echo "match \$USER*"
      ;;
    *) # default
      echo "cond does not match anything"
      ;;
  esac
}

do_case2() {
  case $cond in
    not) test "1" = "0" ;;
  esac
  echo "status: $?"
}

#1
cond="start"     do_case
#2
cond="stop"      do_case
#3
cond="a b c"     do_case
cond={a,b,c}     do_case
#4
cond="123"       do_case
cond="678"       do_case
#5
cond="unknown"   do_case
#6
cond="$HOME"     do_case
#7
cond="$USER"     do_case

#8
cond="not"       do_case2
cond="notmatch"  do_case2

# --- output ---


#1
cond: start
match start

#2
cond: stop
match s*p

#3
cond: a b c
cond does not match anything
cond: {a,b,c}
match {a,b,c}

#4
cond: 123
match [0-5]*|[6-9]+
cond: 678
cond does not match anything

#5
cond: unknown
cond does not match anything

#6
cond: /home/guest
match ~
match fallback

#7
cond: guest
match $USER
match $USER*

#8
status: 1
status: 0

select

select name [in words …]; do commands; done

選択肢を選んだ場合、itemに値が入る。REPLYには、readした値が入る。

select item in dog apple orange;
do
  echo you picked $item \($REPLY\)
  break;
done

# ----- output -----

1) dog
2) apple
3) orange
#? 1
you picked dog (1)

選択肢にない番号を選んだ場合は、nullがセットされる

select item in dog apple orange;
do
  echo you picked $item \($REPLY\)
  break
done

# ----- output -----

1) dog
2) apple
3) orange
#? 4
you picked (4)

何も入力しなかった場合は、再び選択を求められる

select item in dog apple orange;
do
  echo you picked $item \($REPLY\)
  break
done

# ----- output -----

1) dog
2) apple
3) orange
#? 
1) dog
2) apple
3) orange
#? 

変数PS3に値を設定すると、選択時のメッセージをカスタマイズすることができる。

PS3="Please select a package you will install: "
menus=(package1 package2 package3)
select item in ${menus[@]};
do
  test -z "$item" && {
    echo "invalid package, please select again"
    continue
  }
  echo "ok, install $item ($REPLY)"
  break
done

# ----- output -----

1) package1
2) package2
3) package3
Please select a package you will install: hoge
invalid package, please select again
Please select a package you will install: 1
ok, install package1 (1)

((expression))

(( expression ))  
let "expression"

expressionの結果がゼロでない場合、0が返る。そうでない場合は、1。truefalse を指定しても結果は非ゼロである。

以下一通りの算術演算の結果である。

i=0; (( ++i ))      ; echo $?        #=> 0

i=1; (( --i ))      ; echo $?        #=> 1

echo $(( 0 ))                        #=> 0
(( 0 ))       ; echo $?              #=> 1

echo $(( 1 ))                        #=> 1
(( 1 ))       ; echo $?              #=> 0

echo $(( 1 - 1 ))                    #=> 0
(( 1 - 1 ))   ; echo $?              #=> 1

echo $(( 0 + 1 ))                    #=> 1
(( 0 + 1 ))   ; echo $?              #=> 0

echo $(( 1**2 ))                     #=> 1
(( 1**2 ))    ; echo $?              #=> 0

echo $(( 1 * 0 ))                    #=> 0
(( 1 * 0 ))   ; echo $?              #=> 1

echo $(( 0 / 1 ))                    #=> 0
(( 0 / 1 ))   ; echo $?              #=> 1

echo $(( 3 % 3 ))                    #=> 0
(( 3 % 3 ))   ; echo $?              #=> 1

echo $(( 3 % 1 ))                    #=> 0
(( 3 % 1 ))   ; echo $?              #=> 1

echo $(( 2 >> 1 ))                   #=> 1
(( 2 >> 1 ))  ; echo $?              #=> 0

echo $(( 1 << 1 ))                   #=> 2
(( 1 << 1 ))  ; echo $?              #=> 0

echo $(( 1 >= 0 ))                   #=> 1
(( 1 >= 0 ))  ; echo $?              #=> 0

echo $(( 0 <= 0 ))                   #=> 1
(( 0 <= 0 ))  ; echo $?              #=> 0

echo $(( 1 > 0 ))                    #=> 1
(( 1 > 0 ))   ; echo $?              #=> 0

echo $(( 0 < 1 ))                    #=> 1
(( 0 < 1 ))   ; echo $?              #=> 0

echo $(( 1 == 1 ))                   #=> 1
(( 1 == 1 ))  ; echo $?              #=> 0

echo $(( 1 != 0 ))                   #=> 1
(( 1 != 0 ))  ; echo $?              #=> 0

echo $(( 1 & 3 ))                    #=> 1
(( 1 & 3 ))   ; echo $?              #=> 0

echo $(( 1 & 0 ))                    #=> 0
(( 1 & 0 ))   ; echo $?              #=> 1

echo $(( 1 ^ 0 ))                    #=> 1
(( 1 ^ 0 ))   ; echo $?              #=> 0

echo $(( 1 ^ 1 ))                    #=> 0
(( 1 ^ 1 ))   ; echo $?              #=> 1

echo $(( 0 | 1 ))                    #=> 1
(( 0 | 1 ))   ; echo $?              #=> 0

echo $(( 1 | 1 ))                    #=> 1
(( 1 | 1 ))   ; echo $?              #=> 0

echo $(( 1 >= 0 && 1 <= 2 ))         #=> 1
(( 1 >= 0 && 1 <= 2 ))   ; echo $?   #=> 0

echo $(( 0 >= 1 || 1 <= 2 ))         #=> 1
(( 0 >= 1 || 1 <= 2 ))   ; echo $?   #=> 0

echo $(( 1 ? 0 : 1 ))                #=> 0
(( 1 ? 0 : 1 ))   ; echo $?          #=> 1

echo $(( 1, 0 ))                     #=> 0
(( 1, 0 ))   ; echo $?               #=> 1

echo $(( 1, 1 ))                     #=> 1
(( 1, 1 ))   ; echo $?               #=> 0

echo $(( true ))                     #=> 0
(( true ))  ; echo $?                #=> 1

echo $(( false ))                    #=> 0
(( false ))  ; echo $?               #=> 1

[[ expression ]]

[[ expression ]]
# --- commands ---

touch file_ab
line="ls file_*"
#1
[[ $($line) =~ (.+)_(a)b ]]
echo $?
echo "${BASH_REMATCH[@]}"
#2
[[ "$line" =~ (.+)_(a)b ]]
echo $?
echo "${BASH_REMATCH[@]}"
#3
[[ $(cat <(ls file_*) ) =~ (.+)_(a)b ]]
echo $?
echo "${BASH_REMATCH[@]}"
#4
echo ~
[[ ~ =~ /home/([^/]+) ]]
echo $?
echo "${BASH_REMATCH[@]}"
#5
pattern='/home/([^/]+)'
[[ ~ =~ $pattern ]]
echo $?
echo "${BASH_REMATCH[@]}"
#6 - ""すると、#5とは異なるので注意
pattern='/home/([^/]+)'
[[ ~ =~ "$pattern" ]]
echo $?
echo "${BASH_REMATCH[@]}"
:
[[ '/home/([^/]+)' =~ "$pattern" ]]
echo $?
echo "${BASH_REMATCH[@]}"
#7
pattern='\.'

[[ . =~ $pattern ]]
[[ . =~ \. ]]
echo $?
echo "${BASH_REMATCH[@]}"

[[ . =~ "$pattern" ]]
[[ . =~ '\.' ]]
echo $?
echo "${BASH_REMATCH[@]}"

[[ "\." =~ '\.' ]]
echo $?
echo "${BASH_REMATCH[@]}"

# --- output ---


#1
0
file_ab file a

#2
1


#3
0
file_ab file a

#4
/home/guest
0
/home/guest guest

#5
0
/home/guest guest

#6 - ""すると、#5とは異なるので注意
1

0
/home/([^/]+)

#7
0
.
1

0
\.

( expression )

expressionの値を返す。演算子の優先順位を変えたい時とか。

# --- commands ---

i=-1
#1
if (( i > 0 && i < 100 || i < 0 )); then
  echo "#1 true"
else
  echo "#1 false"
fi

#2
if (( i > 0 && ( i < 100 || i < 0 ) )); then
  echo "#2 true"
else
  echo "#2 false"
fi

# --- output ---


#1
#1 true

#2
#2 false

! expression

expressionがtrueならばfalseを返す。

# --- commands ---

#1
if (( 1 > 0 )); then
  echo "true"
else
  echo "false"
fi

#2
if (( !(1 > 0) )); then
  echo "true"
else
  echo "false"
fi

# --- output ---


#1
true

#2
false

expression1 && expression2、expression1 || expression2

expression1で全体の真、偽が決まる場合は、expression2を評価しない。

(( 1 > 0 )) && { echo "expr2"; }     #=> expr2
(( 1 < 0 )) && { echo "expr2"; }     #=> 
(( 1 > 0 )) || { echo "expr2"; }     #=> 
(( 1 < 0 )) || { echo "expr2"; }     #=> expr2

参考 https://www.gnu.org/software/bash/manual/bash.html#Conditional-Constructs

グルーピング

( list )

サブシェルで実行される。変数の割り当ても親シェルには影響しない。
親シェルのPWDはそのままで、一時的にcdして何らかの処理を実行するといった時にも使える。

# --- commands ---

PARAM=10
( PARAM=20 ; echo $PARAM  )
echo $PARAM

mkdir -p subdir
pwd
( cd subdir; pwd;  )
pwd

# --- output ---

20
10
/home/guest/workspace/bash
/home/guest/workspace/bash/subdir
/home/guest/workspace/bash

{ list; }

現在のコンテキストで実行される。}の後にリダイレクトを指定すると、グルーピングされたコマンドの結果をリダイレクト先にまとめて送ることができる。

# --- commands ---

PARAM=10
{ PARAM=20 ; echo $PARAM ; }
echo $PARAM

{
  echo "foo"
  echo "bar"
} > out.txt

cat out.txt

mkdir -p subdir
pwd
{ cd subdir; pwd; }
pwd

# --- output ---

20
20
foo
bar
/home/guest/workspace/bash
/home/guest/workspace/bash/subdir
/home/guest/workspace/bash/subdir

参考 https://www.gnu.org/software/bash/manual/bash.html#Command-Grouping

Coprocesses

coproc [NAME] command [redirections]
# --- commands ---

coproc MY {
  read line
  echo "$line, guest!"
  sleep 1
  echo "hello"
  sleep 1
}

echo ${MY[@]}
echo "hello" >&${MY[1]}
cat - <&${MY[0]}
wait

# --- output ---

63 60
hello, guest!
hello

GNU Parallel

コマンドを並列実行するための機能。Bashにビルドインされているものではない。

https://www.gnu.org/software/parallel/

並列実行であれば、xargs -Pもカジュアルに実行できるグレートな方法である。

https://linuxjm.osdn.jp/html/GNU_findutils/man1/xargs.1.html

関数

name () compound-command [ redirections ]
function name [()] compound-command [ redirections ]
# --- commands ---

:> "$PWD/output"

hello() {
  echo "hello func"
}

# functionがある場合は()は省略可
function hello_with_redirections {
  echo "$@"
} &>> "$PWD/output"

shortfunc() { echo "$FUNCNAME"; }
shortfunc2() { echo "this is ok"; }
# shortfunc3() { echo "this is error, ; is required" }

#1
hello
#2
hello_with_redirections "hello redirections"
#3
cat "$PWD/output"
#4
shortfunc
#5
shortfunc2
# shortfunc3

# --- output ---


#1
hello func

#2

#3
hello redirections

#4
shortfunc

#5
this is ok

関数定義を削除してみる。

# --- commands ---

function myfunc() {
  echo $FUNCNAME
}

# 関数定義の確認
#1
type myfunc ; echo $?
#2
type -t myfunc ; echo $?
#3
myfunc

# 関数定義を削除
#4
unset -f myfunc ; echo $?
#5
type myfunc ; echo $?

# --- output ---


#1
myfunc は関数です
myfunc () 
{ 
    echo $FUNCNAME
}
0

#2
function
0

#3
myfunc

#4
0

#5
type: myfunc: 見つかりません
1

関数の呼び出しスタックを見る。トップレベルの関数名はmainとなる。

# --- commands ---

f0() {
  echo ${FUNCNAME[@]}
}

f1() {
  f0
}

f2() {
  f1
}

#1
f2

# --- output ---


#1
f0 f1 f2 main

変数の可視性について確認する。以下の例を見た方が理解できる。一連の関数の呼び出しにおいて、呼び出し元(caller)が変数の定義をした場合、呼び出される側では、呼び出し側で上書きされた値が見える。

# --- commands ---

function f0() {
  var="change to f0"
  echo "6) After change: In $FUNCNAME, var=$var"
}

function f1() {
  echo "1) In $FUNCNAME, var=$var"
  local var="f1"
  echo "2) Define local: In $FUNCNAME, var=$var"
  unset var
  echo "3) After unset: In $FUNCNAME, var=$var"
  var="change to f1"
  echo "4) After change: In $FUNCNAME, var=$var"
}

function f2() {
  local var="f2"
  f1
  echo "5) In $FUNCNAME, var=$var"
  f0
}

function f3() {
  var="change to f3"
  echo "8) After change: In $FUNCNAME, var=$var"
}

var="global"

f2

echo "7) In $FUNCNAME, var=$var"

f3

echo "9) In $FUNCNAME, var=$var"

# --- output ---

1) In f1, var=f2
2) Define local: In f1, var=f1
3) After unset: In f1, var=
4) After change: In f1, var=change to f1
5) In f2, var=f2
6) After change: In f0, var=change to f0
7) In , var=global
8) After change: In f3, var=change to f3
9) In , var=change to f3

関数定義の確認

# --- commands ---

function my1() {
  :
}

function my2() {
  :
}

typeset -f
typeset -F

# --- output ---

my1 () 
{ 
    :
}
my2 () 
{ 
    :
}
declare -f my1
declare -f my2

変数

name=[value]

+=

+=は、valueへの追加、配列の場合は要素追加する。数値が設定されていても変数属性がintegerとなっていない場合は、単なる文字列結合となるので注意。
変数が配列の場合は、+=で要素を追加すると末尾に追加される。

b=1
b+=1
echo $b #=> 11

# integerとして定義
declare -i a
a=1
a+=1
echo $a #=> 2

# 配列として定義
declare -a arr
arr=(1 2)
arr+=(1)
echo ${arr[*]}  #=> 1 2 1
arr+=1 # この場合は、arr[0]の値に1が追加される
echo ${arr[*]}  #=> 11 2 1

# 連想配列として定義
declare -A map
map=(["a"]=1 ["b"]=2)
map+=(["c"]=5)
echo ${map[@]} #=> 1 2 5
echo ${!map[@]} #=> a b c
map["e"]=6
echo ${!map[@]} #=> a b c e
echo ${map[@]} #=> 1 2 5 6

参照変数

declare -n で参照変数を定義することができる。unset -n で参照を削除する。-nがない場合は、参照先の変数がunsetされる。配列変数は、変数参照指定ができないが、要素には可能である。

# --- commands ---

my() {
  local -n ref=$1
  ref=${ref^^t}
}

var="test"

# 変数名を渡し関数内で操作
my var

#1
echo $var

declare -n ref2=var
declare -n ref3=var

#2 -nオプションありでunset
declare -p ref2
unset -n ref2
declare -p ref2

#3 -nオプションなしでunset
declare -p var
declare -p ref3
unset ref3
declare -p ref3
declare -p var

# --- output ---


#1
TesT

#2 -nオプションありでunset
declare -n ref2="var"
declare -- ref2

#3 -nオプションなしでunset
declare -- var="TesT"
declare -n ref3="var"
declare -n ref3="var"
declare: var: 見つかりません

for文で制御変数を参照設定して操作することも可能である。

# --- commands ---

a="echo a > out"
b="echo b >> out"
c="echo c >> out"
d="cat out"

declare -n ref
for ref in {a,b,c,d}; do
  # a b c dの変数に設定されているコマンド文字列を実行
  eval $ref
done

# --- output ---

a
b
c

位置パラメータ

# --- commands ---
# file: vars2.sh

arg_n=66001

function f() {
  echo $0

  local v= i
  # 1000ずつ飛ばして表示してみる
  for i in $(seq 1 1000 $arg_n); do
    v="\${$i}"
    printf "# $v = %s " "$(eval echo $v)"
  done
}

function f2() {
  echo "in f2: len(args) = $#"
  while [ $# -gt 0 ]; do
    echo $1
    shift
  done
}

#1
echo $0

#2
f $(seq 1 $arg_n | xargs printf 'A%d ')

#3
set a b c d  # 位置パラメータに値を設定
while [ $# -gt 0 ]; do
  echo $1
  shift 2  # 1つ飛ばしで
done

#4
f2 a b c d

#5
echo $-

# --- output ---


#1
vars2.sh

#2
vars2.sh
# ${1} = A1 # ${1001} = A1001 # ${2001} = A2001 # ${3001} = A3001 # ${4001} = A4001 # ${5001} = A5001 # ${6001} = A6001 # ${7001} = A7001 # ${8001} = A8001 # ${9001} = A9001 # ${10001} = A10001 # ${11001} = A11001 # ${12001} = A12001 # ${13001} = A13001 # ${14001} = A14001 # ${15001} = A15001 # ${16001} = A16001 # ${17001} = A17001 # ${18001} = A18001 # ${19001} = A19001 # ${20001} = A20001 # ${21001} = A21001 # ${22001} = A22001 # ${23001} = A23001 # ${24001} = A24001 # ${25001} = A25001 # ${26001} = A26001 # ${27001} = A27001 # ${28001} = A28001 # ${29001} = A29001 # ${30001} = A30001 # ${31001} = A31001 # ${32001} = A32001 # ${33001} = A33001 # ${34001} = A34001 # ${35001} = A35001 # ${36001} = A36001 # ${37001} = A37001 # ${38001} = A38001 # ${39001} = A39001 # ${40001} = A40001 # ${41001} = A41001 # ${42001} = A42001 # ${43001} = A43001 # ${44001} = A44001 # ${45001} = A45001 # ${46001} = A46001 # ${47001} = A47001 # ${48001} = A48001 # ${49001} = A49001 # ${50001} = A50001 # ${51001} = A51001 # ${52001} = A52001 # ${53001} = A53001 # ${54001} = A54001 # ${55001} = A55001 # ${56001} = A56001 # ${57001} = A57001 # ${58001} = A58001 # ${59001} = A59001 # ${60001} = A60001 # ${61001} = A61001 # ${62001} = A62001 # ${63001} = A63001 # ${64001} = A64001 # ${65001} = A65001 # ${66001} = A66001 
#3
a
c

#4
in f2: len(args) = 4
a
b
c
d

#5
hB

特殊パラメータ

$*

$@

位置パラメータに展開される。単語分割されるコンテキストでは、各位置パラメータは個々のwordに展開される。ダブルクオートなしの場合、wordは単語分割される。単語分割が行われないコンテキストでは、各wordをスペースで区切った1つの文字列に展開される。

"$@"が1つの文字列内で展開されるとき、最初のパラメータは前の文字列にジョインされ、最後のパラメータは、後の文字にジョインされる。

"$@"は、"$1" "$2"に等しい。位置パラメータが無しの場合、"$@"や$@は何も無し。

# --- commands ---

function f() {
  echo "$1"
  echo "$2"
  echo "$3"

  echo "in f(): args= $*"

  # $1 $2 ...
  f2 $*

  # "$1" "$2" ...
  f2 "$*"

  # "s $1" ... "$4 z"
  f2 "s $* z"
}

function f2() {
  echo "in f2(): args= $*"
  echo "num: $#"
  echo "$1"
  echo "$2"
  echo "$3"
  echo "$4"
}

function f3() {
  echo "in f3()"
  # $1 $2 ...
  $*
}

function f4() {
  echo "in f4()"
  # "$1" "$2" ...
  "$*"
}

function f5() {
  echo "in f5()"

  # "$10$2" ...
  ( IFS=0 && echo "$*" )
  # "$1$2" ...
  ( IFS= && echo "$*" )

}

f a b c d

f3 "echo hello"
f4 "echo hello"
f5 "echo" "hello"

# --- output ---

a
b
c
in f(): args= a b c d
in f2(): args= a b c d
num: 4
a
b
c
d
in f2(): args= a b c d
num: 1
a b c d



in f2(): args= s a b c d z
num: 1
s a b c d z



in f3()
hello
in f4()
template.sh: 行 71: echo hello: コマンドが見つかりません
in f5()
echo0hello
echohello

$#

位置パラメータの数に展開される。配列などは、${#array[@]}で要素数を取得できる。

# --- commands ---

function f() {
  echo "$1"
  echo "$2"
  echo "$3"

  echo "in f(): args= $@"

  # $1 $2 ...
  f2 $@

  # "$1" "$2" ...
  f2 "$@"

  # "s $1" ... "$4 z"
  f2 "s $@ z"
}

function f2() {
  echo "in f2(): args= $@"
  echo "num: $#"
  echo "$1"
  echo "$2"
  echo "$3"
  echo "$4"
}

function f3() {
  # $1 $2 ...
  $@
}

function f4() {
  # "$1" "$2" ...
  "$@"
}

f a b c d

f3 "echo hello"
f4 "echo hello"

# --- output ---

a
b
c
in f(): args= a b c d
in f2(): args= a b c d
num: 4
a
b
c
d
in f2(): args= a b c d
num: 4
a
b
c
d
in f2(): args= s a b c d z
num: 4
s a
b
c
d z
hello
echo hello: コマンドが見つかりません

$?

foregroundで実行される最近のコマンドの終了ステータスに展開される。

# file: test.sh
echo $@
echo $#

ar=(1 2 3)
echo ${#ar[@]}

# --- output ---

$ ./test.sh 1 2 3
1 2 3
3
3

$-

setコマンドやシェル自身によって設定される現在のオプションフラグに展開される。

# --- commands ---

function f_ret_0() {
  [ 1 -ne 1 ]
  # result should not be 0
}

function f2_ret_0() {
  [ 1 -ne 1 ]
  echo "f2"
  # result should be 0
}

function f3_ret_0() {
  case "$1" in
    -h)
      echo "option -h"
      ;;
  esac
  # result should be 0
}

function f4_ret_not_0() {
  case "$1" in
    -h)
      echo "option -h"
      [ 1 -ne 1 ]
      ;;
  esac
  # result should not be 0
}

#1
(( 1 == 1 ))
echo $?

#2: function f ends with exit status not 0
f_ret_0
echo $?

#3: function f2 ends with exit status 0
f2_ret_0
echo $?

#4: function f3 ends with exit status 0
f3_ret_0 -p
echo $?

#4: function f4 ends with exit status not 0
f4_ret_not_0 -h
echo $?

# --- output ---


#1
0

#2: function f ends with exit status not 0
1

#3: function f2 ends with exit status 0
f2
0

#4: function f3 ends with exit status 0
0

#4: function f4 ends with exit status not 0
option -h
1

対話シェルで実行した場合 (GNU bash, バージョン 4.4.23(1)-release-(x86_64-apple-darwin17.5.0)

set -x                                                                                                                                                              
echo $-                                                                                                                                                             
set +x                                                                                                                                                              
echo $-                                                                                                                                                             

# ----- output -----

+ echo hxB
hxB
+ set +x
hB

$$

プロセスIDに展開される。サブシェルの中では、呼び出し元のプロセスIDに展開される。

$ echo $-
himBH

$!

バックグラウンドで実行された直近のジョブのプロセスIDに展開される。

echo $BASHPID  #=> 42071
echo $$        #=> 42071
bash -c 'echo $BASHPID; echo $$' #=> 42079 42079
( echo $$ )    #=> 42071

例:nohupと組み合わせて使うケース

$ sleep 30 &
[1] 15816
$ echo $!
15816
$ echo "hello"
hello
$ echo $!
15816
$ sleep 30 &
[2] 15870
$ echo $!
15870
$ jobs -l
[1]- 15816 Running                 sleep 30 &
[2]+ 15870 Running                 sleep 30 &

$0

シェルスクリプト名に展開される。コマンドファイルで呼び出された場合は、ファイル名に展開される。-cオプションで呼び出された場合は、-c string arguments...となる場合の最初の引数が入る。それ以外では、Bashを呼び出すのに使用されたファイルの名前が入る。

#!/usr/bin/env bash

pidfile=job.pids
nohup bash -c 'i=0; while ((i<10)); do echo job1-$i; ((i++)); done' > stdout-1 2> stderr-1 &
echo $! > job.pids
nohup bash -c 'i=0; while ((i<10)); do echo job2-$i; ((i++)); done' > stdout-2 2> stderr-2 &
echo $! >> job.pids
# hello.sh
echo $0

# ---------

./hello.sh    #=> ./hello.sh
bash hello.sh #=> hello.sh
bash -c 'echo $0'       #=> bash
bash -c 'echo $0' hello #=> hello

$_

最後に実行されたコマンドの最後の引数に展開される。

$ echo $0
bash
$ /bin/bash
$ echo $0
/bin/bash
$ ln -s /bin/bash hogehoge
$ ./hogehoge
$ echo $0
./hogehoge

_ をシェルの変数名として設定することはできない、特殊パラメータとして予約されているためである。

# --- commands ---

function f() {
  echo "${1:-func}"
}

echo '#!/bin/bash
echo $_
' > com.sh
chmod +x com.sh

#1
ls -G > /dev/null
echo $_

#2
bash -c "/bin/echo"
echo $_

#3
bash -c "/bin/echo hello"
echo $_

#4
f
echo $_

#5
f func_arg
echo $_

#6
./com.sh
echo $_

#7
./com.sh hello
echo $_

# --- output ---


#1
-G

#2

/bin/echo

#3
hello
/bin/echo hello

#4
func
f

#5
func_arg
func_arg

#6
./com.sh
./com.sh

#7
./com.sh
hello

配列の操作

連想配列の操作

展開

展開の種類には以下がある。展開の順序を知っておくことで、最終的にどのような結果が得られるのか迷わずに済むだろう。
以下の順序で展開が行われる。(ただし、二重引用符や単一引用符で囲まれる場合は、いくつかの展開がスキップされたりするがここでは割愛する)

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

全ての展開後に、クオートの除去( \ ' " )が行われる。コマンド検索が行われ、コマンド実行という流れとなる。

参考 https://www.gnu.org/software/bash/manual/bash.html#Shell-Expansions

ブレース展開

{x..y[..incr]}
$ _=hoge
$ echo $_
 

参考 https://www.gnu.org/software/bash/manual/bash.html#Brace-Expansion

チルダ展開

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

~$HOME
~<user>ユーザ<user>の$HOME
~+$PWD
~-${OLDPWD:'~-'}
~N、~+Ndirs +N
~-Ndirs -N

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

name[subscript]=value
declare -a name
declare -a name[subscript]
  注:subscriptは無視される
name=(value1 value2 … )

参考 https://www.gnu.org/software/bash/manual/bash.html#Tilde-Expansion

パラメータ展開

${parameter}

parameterの値に置換される。

# --- commands ---

#1: 代入
arr=
arr[0]=1
echo ${a[@]}
arr[5]=1
echo ${arr[@]}
echo ${#arr[@]}

#2: 宣言
declare -a arr
arr[0]=1
echo ${arr[@]}

#3: 初期化
arr=(1 2 3 4)
echo ${arr[@]}

#4: 繰り返し
arr=(1 2 3 4)
for item in ${arr[@]}; do
  echo $item
done

#5: 削除
arr=(1 2 3 4)
while (( ${#arr[@]} > 0 )); do
  echo ${arr[0]}
  unset arr[0]
  arr=(${arr[@]})
done

#6: まとめて削除
arr=(1 2 3 4)
declare -p arr
echo ${arr[*]}
unset arr
declare -p arr

#7: まとめて削除
arr=(1 2 3 4)
declare -p arr
unset arr[*]
declare -p arr

#8: インデックスで繰り返し
arr=(1 2 3 4)
for i in ${!arr[*]};
  echo ${arr[$i]}
done

# --- output ---


#1: 代入

1 1
2

#2: 宣言
1

#3: 初期化
1 2 3 4

#4: 繰り返し
1
2
3
4

#5: 削除
1
2
3
4

#6: まとめて削除
declare -a arr=([0]="1" [1]="2" [2]="3" [3]="4")
1 2 3 4
declare -- arr

#7: まとめて削除
declare -a arr=([0]="1" [1]="2" [2]="3" [3]="4")
declare -- arr

#8: インデックスで繰り返し
template.sh: eval: 行 83: 予期しないトークン `echo' 周辺に構文エラーがあります
template.sh: eval: 行 83: `  echo ${arr[$i]}'


[2019-03-06 22:35:36] t-moriyasu@ToruMoriyasu-no-iMac ~/workspace/bash 
$ bash vars7.sh 
# --- commands ---

#1: 代入
arr=
arr[0]=1
echo ${a[@]}
arr[5]=1
echo ${arr[@]}
echo ${#arr[@]}

#2: 宣言
declare -a arr
arr[0]=1
echo ${arr[@]}

#3: 初期化
arr=(1 2 3 4)
echo ${arr[@]}

#4: 繰り返し
arr=(1 2 3 4)
for item in ${arr[@]}; do
  echo $item
done

#5: 削除
arr=(1 2 3 4)
while (( ${#arr[@]} > 0 )); do
  echo ${arr[0]}
  unset arr[0]
  arr=(${arr[@]})
done

#6: まとめて削除
arr=(1 2 3 4)
declare -p arr
echo ${arr[*]}
unset arr
declare -p arr

#7: まとめて削除
arr=(1 2 3 4)
declare -p arr
unset arr[*]
declare -p arr

#8: インデックスで繰り返し
arr=(1 2 3 4)
for i in ${!arr[*]}; do
  # iは、0、1 ...
  echo ${arr[$i]}
done

# --- output ---


#1: 代入

1 1
2

#2: 宣言
1

#3: 初期化
1 2 3 4

#4: 繰り返し
1
2
3
4

#5: 削除
1
2
3
4

#6: まとめて削除
declare -a arr=([0]="1" [1]="2" [2]="3" [3]="4")
1 2 3 4
declare -- arr

#7: まとめて削除
declare -a arr=([0]="1" [1]="2" [2]="3" [3]="4")
declare -- arr

#8: インデックスで繰り返し
1
2
3
4

${parameter:-word}

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

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

declare -A name

${parameter:=word}

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

# --- commands ---

#1: 宣言
declare -A map
map=([apple]=120 [orange]=100 [grape]=150)
declare -p map

#2: 参照
echo ${map[apple]}

#3: 繰り返し
for key in ${!map[@]}; do
  printf "%s = %s\n" "$key" "${map[$key]}"
done

#4: 繰り返し2
for value in ${map[@]}; do
  printf "%s\n" "$value"
done

#5: 削除
unset map[apple]
declare -p map

#6: 全削除
unset map
declare -p map

# --- output ---


#1: 宣言
declare -A map=([grape]="150" [apple]="120" [orange]="100" )

#2: 参照
120

#3: 繰り返し
grape = 150
apple = 120
orange = 100

#4: 繰り返し2
150
120
100

#5: 削除
declare -A map=([grape]="150" [orange]="100" )

#6: 全削除
declare -- map

${parameter:?word}

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

${parameter:+word}

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

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

${parameter:offset}、${parameter:offset:length}

#!/usr/bin/env bash
PARAM="parameter"
echo $PARAM  #=> parameter

${!prefix*}、${!prefix@}

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

echo $USER #=> "moritetu"

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

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

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

# ${parameter:=word}

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

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

${#parameter}

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

# ${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}、${parameter##word}

# ${parameter:+word}

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

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

${parameter%word}、${parameter%%word}

# ${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

${parameter/pattern/string}

# ${!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

${parameter^pattern}、${parameter^^pattern}、${parameter,pattern}、${parameter,,pattern}

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

参考 https://www.gnu.org/software/bash/manual/bash.html#Shell-Parameter-Expansion

コマンド置換

算術展開

プロセス置換

サンプル lsの結果で*.shファイルのみを出力

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

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

サンプル a.txtとb.txtをdiffの入力ファイルとして比較

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="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

単語分割

ファイル名展開

パターンマッチング

リダイレクション

execを使った入出力のリダイレクト

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

execを使った入力のリダイレクト
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 ${array[*]//i/I}           #=> # thIs Is a comment lIne. # thIs Is not 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. This is not a comment line.

echo ${parameter/%./\!}          #=> # this is a comment line!
echo ${parameter/%e/\!}          #=> # this is a comment line.
echo ${parameter[@]/%./!}        #=> # this is a comment line! # this is not a comment line!
execを使った出力のリダイレクト
parameter="hellO, WOrld"        #=> 
array=("hellO, WOrld" "hellO, WOrld2")  #=> 

# ${parameter^pattern}
echo ${parameter^[lh]}          #=> 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

使い道はさておき、こんなのもできる。

$(command)
or
`command`

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

複合コマンドの出力をファイルにリダイレクトする

# --- commands ---

#1: Capture stdout
stdout=$(echo "hello")
echo $stdout

#2: Capture stderr
stderr=$(echo "hello" >&2)
echo $stderr

#3: Capture stdout, stderr
std=$(echo "stdout"; echo "stderr" >&2)
echo $std

#4: Read from stdin
echo "pwd" > pwd.txt
$(<pwd.txt)

#5: Split data into array
:> num.txt
for i in $(seq 1 10); do
  echo "$i col1 col2" >> num.txt
done
OLDIFS="$IFS"
IFS="
"
lines=( $(<num.txt) )
IFS="$OLDIFS"
for l in "${lines[@]}"; do
  echo $l
done

#6: Nest
echo $(echo $(pwd))
echo `echo \`pwd\``

# --- output ---


#1: Capture stdout
hello

#2: Capture stderr
hello


#3: Capture stdout, stderr
stderr
stdout

#4: Read from stdin
/home/guest/workspace/bash

#5: Split data into array
1 col1 col2
2 col1 col2
3 col1 col2
4 col1 col2
5 col1 col2
6 col1 col2
7 col1 col2
8 col1 col2
9 col1 col2
10 col1 col2

#6: Nest
/home/guest/workspace/bash
/home/guest/workspace/bash

参考リンク


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