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に展開される。そのコンテキストでは、単語分割とパス名展開が行われる。ダブルクオート内で囲まれている場合は、個々のパラメータをIFS変数に指定されている最初の文字で連結された1つの単語に展開される。

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

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

"$*"は、"$1c$2..."に等しい。cはIFSに指定されている先頭の文字。IFSがunsetされる場合はスペースで区切られる。IFSがnullならば、区切り文字なしで結合される。

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

$@

位置パラメータに展開される。単語分割されるコンテキストでは、各位置パラメータは個々の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() {
  # $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: コマンドが見つかりません

$#

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

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

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

# --- output ---

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

$?

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

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

$-

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

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

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

+ echo hxB
hxB
+ set +x
hB

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

$ echo $-
himBH

$$

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

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

$!

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

$ 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 &

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

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

$0

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

# 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

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

$ _=hoge
$ echo $_
 

配列の操作

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

配列の主な操作は下例のサンプルの通り。

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

連想配列の操作

declare -A name
# --- 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

展開

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

  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]}
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

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

チルダ展開

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

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

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

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

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

パラメータ展開

${parameter}

parameterの値に置換される。

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

${parameter:-word}

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

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

echo $USER #=> "moritetu"

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

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

${parameter:=word}

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

# ${parameter:=word}

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

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

${parameter:?word}

# ${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} の逆。

# ${parameter:+word}

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

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

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

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

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

# ${!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[@]}

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}

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

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

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}

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}

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!

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

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

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

コマンド置換

$(command)
or
`command`

コマンド置換は、サブシェルでの実行結果に展開される。$(cat file)は、$(<file)と等しい。$(<file)の方が高速である。ネストすることも可能である。
``は古いスタイルの記述方法。$()の方がバックスラッシュとか気にしなくても良い。ダブルクォートで囲まれたコマンド置換では、単語分割やファイル名展開は行われない。

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

算術展開

$(( expression ))

全体が、ダブルクォートで囲まれていても展開される。
パラメータ展開、コマンド置換、クォート除去は行われる。結果は算術式として評価される。算術展開はネスト可能。

# +++++++++++++++ Commands +++++++++++++++
#1: 計算
echo $(( 1 + 2 ))
echo "$(( 100 * 20 ))"

# [Output] >
#1: 計算
3
2000


# +++++++++++++++ Commands +++++++++++++++
#2: 変数を使った場合
#   Shell VariablesはOK、パラメータ$はなくても良い
i=10
echo $(( i + 9 ))
echo $(( $i + 9 ))
echo $(( "$i" + 9 ))

# [Output] >
#2: 変数を使った場合
19
19
19


# +++++++++++++++ Commands +++++++++++++++
#3: 関数を使ってみるがダメ
counter() {
  echo 1
}
echo $(( counter * 10 ))

# [Output] >
#3: 関数を使ってみるがダメ
0


# +++++++++++++++ Commands +++++++++++++++
#4: ネスト
echo $(( $(( 1 + 10 )) * 10 ))
echo $(( "$(( 1 + 10 ))" * 10 ))

# [Output] >
#4: ネスト
110
110

関連 算術式

プロセス置換

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

  1. a=
  2. while IFS= read -r line
  3. do
  4.   a="$line:$a"
  5. done < <(ls . | grep -e "\.sh$")

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

  1. diff -u <(cat a.txt) <(cat b.txt)

サンプル

  1. exec 3>&1
  2. exec > >(while read line; do echo "$(date): $line";done)
  3.  
  4. echo "hello"
  5. echo "bar"
  6.  
  7. exec 1>&3 3>&-

参考

単語分割

シェルは、ダブルクォートで囲まれてない、パラメータ展開、コマンド置換、算術展開の結果をスキャンし単語分割する。

IFSパラメータにセットされている各文字をフィールド区切り文字として単語に分割する。

# +++++++++++++++ Commands +++++++++++++++
#1: <space>, <tab>, <newline>を含む文字列
a="This is
  at	test."
array=( $a )
for i in {0..3}; do
  echo ${array[$i]}
done

# [Output] >
#1: <space>, <tab>, <newline>を含む文字列
This
is
at
test.


# +++++++++++++++ Commands +++++++++++++++
#2: IFSをunsetする
#   <space>, <tab>, <newline>が作用する
unset IFS
a="This is
  at	test."
array=( $a )
for i in {0..3}; do
  echo ${array[$i]}
done

# [Output] >
#2: IFSをunsetする
This
is
at
test.


# +++++++++++++++ Commands +++++++++++++++
#3: IFSを別の区切り文字にする
IFS='|_'
a="This is
  at	test.|foo_bar_|_1"
array=( $a )
for i in {0..5}; do
  echo ${array[$i]}
done

# [Output] >
#3: IFSを別の区切り文字にする
This is
  at	test.
foo
bar


1


# +++++++++++++++ Commands +++++++++++++++
#4: IFSがnullの場合は分割されない
IFS=
a="This is
  at	test.|foo_bar_|_1"
array=( $a )
for i in {0..1}; do
  echo ${array[$i]}
done

# [Output] >
#4: IFSがnullの場合は分割されない
This is
  at	test.|foo_bar_|_1



# +++++++++++++++ Commands +++++++++++++++
#5: クォートで囲まれた動作
function myfunc() {
  echo "args: $@"
  echo "len : $#"
}

#5:1
a=
myfunc $a
myfunc "$a"

#5:1
a="1 2"
myfunc $a
myfunc "$a"

# [Output] >
#5: クォートで囲まれた動作
#5:1
args: 
len : 0
args: 
len : 1
#5:1
args: 1 2
len : 2
args: 1 2
len : 1

ファイル名展開

単語分割の後、-fオプションが設定されていなければ、*?[ の各文字がスキャンされる。これらの文字の1つが出現した場合、パターンとして認識し、パターンにマッチするアルファベット順に整列されたファイルリストに置換される。マッチするファイルが見つからず、nullglobオプションが無効の場合、単語はそのまま残される。nullglobオプションが設定されており、マッチするファイルが見つからない場合、単語は削除される。failglobオプションが設定されており、マッチするファイルが見つからない場合、エラーメッセージが表示されコマンドは実行されない。nocaseglobオプションが有効な場合、マッチングはアルファベットの大小文字に関わらず実行される。

ファイル名展開にパターンが使われるとき、dotglobオプションが設定されていないならば、ファイル名の先頭または / の直後の.はマッチする。... は、dotglobオプションに関わらずマッチする。

GLOBIGNORE変数が設定されている場合、その変数に指定されるパターンにマッチするファイルはリストから除かれる。GLOBIGNOREが設定されている場合、doglobオプションが有効であるのと同じ効果をもつ。

# +++++++++++++++ Commands +++++++++++++++
#1: File matching
tree -a filenames/

#1-1: [...]
ls filenames/[WQ]ORLD.txt

#1-2: ?
ls filenames/?ORLD.txt

#1-3: *
ls filenames/*.txt

# [Output] >
#1: File matching
filenames/
├── .helloworld.txt
├── QORLD.txt
├── WORLD.txt
└── hello.txt

0 directories, 4 files
#1-1: [...]
filenames/QORLD.txt
filenames/WORLD.txt
#1-2: ?
filenames/QORLD.txt
filenames/WORLD.txt
#1-3: *
filenames/QORLD.txt
filenames/WORLD.txt
filenames/hello.txt


# +++++++++++++++ Commands +++++++++++++++
#2: dotglob
tree -a filenames/

#2-1: dotglob off
ls filenames/*.txt

#2-2: dotglob on
shopt -s dotglob
ls filenames/*.txt

# [Output] >
#2: dotglob
filenames/
├── .helloworld.txt
├── QORLD.txt
├── WORLD.txt
└── hello.txt

0 directories, 4 files
#2-1: dotglob off
filenames/QORLD.txt
filenames/WORLD.txt
filenames/hello.txt
#2-2: dotglob on
filenames/.helloworld.txt
filenames/QORLD.txt
filenames/WORLD.txt
filenames/hello.txt


# +++++++++++++++ Commands +++++++++++++++
#3: nocaseglob
tree -a filenames/

#3-1: nocaseglob off
ls filenames/[wq]orld.txt

#3-2: nocaseglob on
shopt -s nocaseglob
ls filenames/[wq]orld.txt

# [Output] >
#3: nocaseglob
filenames/
├── .helloworld.txt
├── QORLD.txt
├── WORLD.txt
└── hello.txt

0 directories, 4 files
#3-1: nocaseglob off
ls: filenames/[wq]orld.txt: No such file or directory
#3-2: nocaseglob on
filenames/QORLD.txt
filenames/WORLD.txt


# +++++++++++++++ Commands +++++++++++++++
#4: nullglob
cd filenames
tree -a .

#4-1: nullglob off
ls [wq]orld.txt

#4-2: nullglob on
shopt -s nullglob
ls [wq]orld.txt

# [Output] >
#4: nullglob
.
├── .helloworld.txt
├── QORLD.txt
├── WORLD.txt
└── hello.txt

0 directories, 4 files
#4-1: nullglob off
ls: [wq]orld.txt: No such file or directory
#4-2: nullglob on
QORLD.txt
WORLD.txt
hello.txt


# +++++++++++++++ Commands +++++++++++++++
#5: failglob
cd filenames
tree -a .

#5-1: failglob off
ls [wq]orld.txt

#5-2: failglob on
shopt -s failglob
ls [wq]orld.txt

# [Output] >
#5: failglob
.
├── .helloworld.txt
├── QORLD.txt
├── WORLD.txt
└── hello.txt

0 directories, 4 files
#5-1: failglob off
ls: [wq]orld.txt: No such file or directory
#5-2: failglob on
template.sh: 行 43: 一致しません: [wq]orld.txt
# +++++++++++++++ Commands +++++++++++++++
#6: GLOBIGNORE
cd filenames
ls *

#6-1: set GLOBIGNORE="*o.txt"
GLOBIGNORE="*o.txt"
ls *

#6-2: set GLOBIGNORE="*o.txt:.*"
#     .で始まるファイルを除外したい
GLOBIGNORE="*o.txt:.*"
ls *

#6-3: unset GLOBIGNORE
unset GLOBIGNORE
ls *

# [Output] >
#6: GLOBIGNORE
QORLD.txt
WORLD.txt
hello.txt
#6-1: set GLOBIGNORE=*o.txt
.helloworld.txt
QORLD.txt
WORLD.txt
#6-2: set GLOBIGNORE=*o.txt:.*
QORLD.txt
WORLD.txt
#6-3: unset GLOBIGNORE
QORLD.txt
WORLD.txt
hello.txt

パターンマッチング

*

nullを含む任意の文字列。globstarオプションがonの時、**はサブディレクトリにもマッチする。

$ tree -a filenames/
filenames/
├── .helloworld.txt
├── QORLD.txt
├── WORLD.txt
├── hello.txt
└── subidr
    ├── hello.txt
    └── sub2dir
        └── hello.txt

2 directories, 6 files
$ ls ./filenames/**/*.txt
./filenames/subidr/hello.txt
$ shopt -s globstar
$ ls ./filenames/**/*.txt
./filenames/QORLD.txt                ./filenames/subidr/hello.txt
./filenames/WORLD.txt                ./filenames/subidr/sub2dir/hello.txt
./filenames/hello.txt
?

任意の1文字

$ tree -a filenames/
filenames/
├── .helloworld.txt
├── QORLD.txt
├── WORLD.txt
├── hello.txt
└── subidr
    ├── hello.txt
    └── sub2dir
        └── hello.txt

2 directories, 6 files
$ ls filenames/?ello.txt
filenames/hello.txt
[…]

[ ]で囲まれる文字のいずれかにマッチする。ハイフンで区切られた文字ペアは範囲を示す。

例:[a-d]  -> [abcd]

[![^はnot matchとなる。

$ tree -a filenames/
filenames/
├── .helloworld.txt
├── QORLD.txt
├── WORLD.txt
├── hello.txt
└── subidr
    ├── hello.txt
    └── sub2dir
        └── hello.txt

2 directories, 6 files
$ ls filenames/[^h]*.txt
filenames/QORLD.txt filenames/WORLD.txt
$ ls filenames/[!h]*.txt
filenames/QORLD.txt filenames/WORLD.txt

文字クラス

以下で、POSIX標準で定義されている文字クラスが使用可能。

[:class:]

class are:
  alnum   alpha   ascii   blank   cntrl   digit   graph   lower
  print   punct   space   upper   word    xdigit

wordは、文字、数値、_にマッチする。

$ tree filenames/class/
filenames/class/
└── hoge_foo.txt

0 directories, 1 file
$ ls filenames/class/hoge[[:word:]]foo.txt
filenames/class/hoge_foo.txt
$ ls filenames/class/hoge[[:ascii:]]foo.txt
filenames/class/hoge_foo.txt
$ ls filenames/class/hoge[[:alpha:]]foo.txt
ls: filenames/class/hoge[[:alpha:]]foo.txt: No such file or directory

等価クラス([=c=])

cと等価なキャラクタにマッチする。
[=e=]は、 è、 é、 ê、 ë などにもマッチする。

$ ls
a  á  à  â  å  ä  ã  ª  anaconda-ks.cfg  file_e.txt  file_é.txt  original-ks.cfg
$ ls | grep "[[=a=]]"
a
á
à
â
å
ä
ã
ª
anaconda-ks.cfg
original-ks.cfg
$ ls | grep "a"
a
anaconda-ks.cfg
original-ks.cfg

照合シンボル([.symbol.])

照合シンボルにマッチする。

$ ls Hello*
Hello           Hello|World.txt
$ ls Hello[[.vertical-line.]]World.txt
Hello|World.txt
$ ls stdout-*
stdout-1 stdout-2
$ ls stdout[[.dash.]][12]
stdout-1 stdout-2

参考


extglobで有効なパターンマッチング

以下の5つのパターンマッチングは、extglobオプションが有効な場合に使用可能である。

shopt -s nullglob
for conf in /etc/*.conf; do
  echo $conf
done
shopt -u nullglob

pattern-listには、 | で区切った1つ以上のパターンが入る。

先頭の、?、*、+、@、!、は正規表現式で使用されるメタ記号の意味と同様。@は、指定されるパターンが1つ含まれることを示す。([characters]のようなものか?)

?(pattern-list)

指定されたパターンにマッチするものが0または1つある。

shopt -s extglob
*(pattern-list)

指定されたパターンにマッチするものが、0以上ある。

# +++++++++++++++ Commands +++++++++++++++
#1: ?(pattern-list)
tree .

#1-1: case: extglob off
#ls foo?(_*).txt

#1-2: case: extglob on
shopt -s extglob
ls foo?(_*).txt

# ***** <[Output]> ***** 
#1: ?(pattern-list)
.
├── bar.txt
├── foo.txt
└── foo_bar.txt

0 directories, 3 files
#1-1: case: extglob off
予期しないトークン `(' 周辺に構文エラーがあります
`ls foo?(_*).txt'
#1-2: case: extglob on
foo.txt
foo_bar.txt
+(pattern-list)

指定されたパターンにマッチするものが、1つ以上ある。

# +++++++++++++++ Commands +++++++++++++++
#2: *(pattern-list)
shopt -s extglob

tree .

#2-1:
ls *(foo*)bar.txt

# ***** <[Output]> ***** 
#2: *(pattern-list)
.
├── bar.txt
├── foo.txt
└── foo_bar.txt

0 directories, 3 files
#2-1:
bar.txt
foo_bar.txt
@(pattern-list)

指定したパターンにマッチするものが1つある。

# +++++++++++++++ Commands +++++++++++++++
#3: +(pattern-list)
shopt -s extglob

tree .

#3-1: bar.txtはマッチしないはず
ls +(foo*)bar.txt

# ***** <[Output]> ***** 
#3: +(pattern-list)
.
├── bar.txt
├── foo.txt
└── foo_bar.txt

0 directories, 3 files
#3-1: bar.txtはマッチしないはず
foo_bar.txt
!(pattern-list)

指定したパターンにマッチしない。

# +++++++++++++++ Commands +++++++++++++++
#4: @(pattern-list)
shopt -s extglob

tree .

#4-1: *との違いを確認
ls foo*(_bar).txt

#4-2: *との違いを確認
ls foo@(_bar).txt

# ***** <[Output]> ***** 
#4: @(pattern-list)
.
├── bar.txt
├── foo.txt
└── foo_bar.txt

0 directories, 3 files
#4-1: *との違いを確認
foo.txt
foo_bar.txt
#4-2: *との違いを確認
foo_bar.txt

リダイレクション

コマンドの入出力先を変更したり操作することができる。リダイレクトは、左から右に表示されている順で処理される。

入力のリダイレクト

# +++++++++++++++ Commands +++++++++++++++
#5: !(pattern-list)
shopt -s extglob

tree .

#5-1:
ls foo!(_bar).txt

# ***** <[Output]> ***** 
#5: !(pattern-list)
.
├── bar.txt
├── foo.txt
└── foo_bar.txt

0 directories, 3 files
#5-1:
foo.txt

wordからの入力のために記述子 n がオープンされる。nの指定がない場合は、標準入力になる。

[n]<word

出力のリダイレクト

# +++++++++++++++ Commands +++++++++++++++
echo "line1
line2" > filename.out

while read line; do
  echo $line
done < filename.out

# ***** <[Output]> ***** 
line1
line2

wordへの出力のため記述子 n を開く。nの指定がない場合は、標準出力となる。wordの評価の結果のファイルが存在しない場合は、新しく作成される。存在していた場合は、サイズ0に切り詰められる。

noclobberオプションが設定されている場合、上書きが防止される。>|が指定されている場合は、上書きできる。

[n]>[|]word

追記型出力のリダイレクト

# +++++++++++++++ [TEST-2 Commands] +++++++++++++++
# ファイルが存在しない場合は新規作成される
echo "line1
line2" > filename.out
cat filename.out

# ***** [TEST-2 Output] ***** 
line1
line2


# +++++++++++++++ [TEST-3 Commands] +++++++++++++++
# noclobberが有効の場合は上書きできない
:>filename.out
set -o noclobber
echo "line1
line2" > filename.out

# ***** [TEST-3 Output] ***** 
hoge.sh: 行 40: filename.out: 存在するファイルを上書きできません


# +++++++++++++++ [TEST-4 Commands] +++++++++++++++
# >| の場合は上書きできる
:>filename.out
set -o noclobber
echo "line1
line2" >| filename.out
cat filename.out

# ***** [TEST-4 Output] ***** 
line1
line2

wordの評価の結果に追記出力するために記述子 nがオープンされる。nの指定がない場合は標準出力となる。ファイルが存在しない場合は新規に作成される。

[n]>>word

標準出力と標準エラー出力のリダイレクト

# +++++++++++++++ [TEST-5 Commands] +++++++++++++++
# >>は追記

# 新規作成
echo "[1]line1" > filename.out
cat filename.out
# 追記
echo "[2]line2" >> filename.out
cat filename.out
# 切り詰め
echo "[3]line1" > filename.out
cat filename.out

# ***** [TEST-5 Output] ***** 
[1]line1
[1]line1
[2]line2
[3]line1
&>word
>&word

以下と等価

>word 2>&1

ヒアドキュメント

# +++++++++++++++ [TEST-6 Commands] +++++++++++++++
# >& word
# &> word
# >word 2>&1

dump() {
  echo "stdout"
  echo "stderr" >&2
}

dump > stdout.out 2> stderr.out
#1 stdout.out
cat stdout.out

#2 stderr.out
cat stderr.out

#3 stdout-stderr.out (&>)
dump &> stdout-stderr.out
cat stdout-stderr.out

#4 stdout-stderr.out (2>&1)
dump > stdout-stderr.out 2>&1
cat stdout-stderr.out

# ***** [TEST-6 Output] ***** 
# >& word
# &> word
# >word 2>&1
#1 stdout.out
stdout
#2 stderr.out
stderr
#3 stdout-stderr.out (&>)
stdout
stderr
#4 stdout-stderr.out (2>&1)
stdout
stderr

wordのみが現れる(後にスペースなどが含まれてはいけない)行まで入力を読む。読み込みされた行は、標準入力として扱われる。

wordがクォートで囲まれている場合、delimeterはクオート除去された結果となり、here-documentは展開されない。wordがクォートで囲まれていない場合、変数展開、コマンド置換、算術展開は行われ、\newline(改行)は無視される。\、$、`をエスケープするために\をつける必要がある。

<<-が使われる場合、全ての行頭のタブ文字は取り除かれる。

[n]<<[-]word
        here-document
delimiter

wordは変数でも可能か?(マニュアルにはNo parameter and variable expansion, ... is performed on word とあるが。。)。試すと正しく展開されているようである。

# +++++++++++++++ [TEST-7 Commands] +++++++++++++++
# ヒアドキュメント <<word
param=hello
cat <<STRING
$param
\$param
$(pwd)
STRING

# ***** [TEST-7 Output] ***** 
# ヒアドキュメント <<word
hello
$param
/home/guest/workspace/bash


# +++++++++++++++ [TEST-8 Commands] +++++++++++++++
# ヒアドキュメント <<"word"
param=hello
cat <<"STRING"
$param
$(pwd)
STRING

# ***** [TEST-8 Output] ***** 
# ヒアドキュメント <<word
$param
$(pwd)


# +++++++++++++++ [TEST-9 Commands] +++++++++++++++
# ヒアドキュメント <<'word'

#1: 全体をクォート
param=hello
cat <<'STRING'
$param
$(pwd)
STRING

#2: 一部をクォート
param=hello
cat <<'STR'ING
$param
$(pwd)
STRING

# ***** [TEST-9 Output] ***** 
# ヒアドキュメント <<'word'
#1: 全体をクォート
$param
$(pwd)
#2: 一部をクォート
$param
$(pwd)


# +++++++++++++++ [TEST-10 Commands] +++++++++++++++
# ヒアドキュメント <<-word
param=hello
cat <<-STRING
		$param
		$(pwd)
STRING

# ***** [TEST-10 Output] ***** 
# ヒアドキュメント <<-word
hello
/home/guest/workspace/bash

ヒアストリング

# +++++++++++++++ [TEST-11 Commands] +++++++++++++++
# wordでの変数展開は無効である

param=STRING
cat <<$(echo $param)
	$param
	$(pwd)
$(echo $param)
echo foo

# ***** [TEST-11 Output] ***** 
# wordでの変数展開は無効である
	STRING
	/home/guest/workspace/bash
foo

wordは、パス名展開、単語分割は行われない。結果は単一の文字列(改行あり)として入力に渡される。

[n]<<< word

ファイルディスクリプタの複製

# +++++++++++++++ [TEST-12 Commands] +++++++++++++++
#1 ヒアストリング
read first second <<< "Hello World"
echo $first
echo $second

# ***** [TEST-12 Output] ***** 
#1 ヒアストリング
Hello
World


# +++++++++++++++ [TEST-13 Commands] +++++++++++++++
#2 以下でも同様なことが可能
echo "Hello World" | {
  read first second
  echo $first
  echo $second
}

# ***** [TEST-13 Output] ***** 
#2 以下でも同様なことが可能
Hello
World


# +++++++++++++++ [TEST-14 Commands] +++++++++++++++

#3 変数展開
param1=Hello
param2=World
read first second <<< "$param1 $param2"
echo $first
echo $second

# ***** [TEST-14 Output] ***** 
#3 変数展開
Hello
World


# +++++++++++++++ [TEST-15 Commands] +++++++++++++++
#4 関数で入力を処理、パイプでもできるが・・・
param1=Hello
param2=World
function my() {
  cat -
}
my <<< "$param1 $param2"
echo "$param1 $param2" | my

# ***** [TEST-15 Output] ***** 
#4 関数で入力を処理、パイプでもできるが・・・
Hello World
Hello World

標準入力の複製

lsofコマンドでディスクリプタの使用状況も合わせて確認している。

# 入力
[n]<&word
# 出力
[n]>&word

標準出力の複製

# ++++++++++++++++++++++ [TEST-1 Commands] ++++++++++++++++++++++
# 入力用のファイルを作成
:> input.txt
for i in {1..5}; do
  echo $i >> input.txt
done
#lsof -p $$

# 標準入力0を複製し記述子60とする
exec 60<&0
lsof -p $$

# 標準入力をinput.txtにする
exec < input.txt
#lsof -p $$

# input.txtから読み込み
for i in {1..5}; do
  read line && echo $line
done

# 標準入力を戻し、記述子60を閉じる
exec 0<&60-
lsof -p $$


# ******** [TEST-1 Output] ********
COMMAND   PID       USER   FD   TYPE             DEVICE   SIZE/OFF     NODE NAME
bash    47422 guest  cwd    DIR                1,8       2312 32874335 /home/guest/workspace/bash
bash    47422 guest  txt    REG                1,8     945256 12433654 /usr/local/Cellar/bash/4.4.23/bin/bash
bash    47422 guest  txt    REG                1,8     841456 32850597 /usr/lib/dyld
bash    47422 guest  txt    REG                1,8 1175449600 32853756 /private/var/db/dyld/dyld_shared_cache_x86_64
bash    47422 guest    0r   REG                1,8        402 36951947 /private/var/folders/jt/gf6q8xn50szb_fbqq1j36yzh0000gn/T/sh-thd.fQezbf
bash    47422 guest    1   PIPE 0xcc5656065833aed5      16384          ->0xcc5656065833ae15
bash    47422 guest    2   PIPE 0xcc5656065833ab15      16384          ->0xcc5656065833af95
bash    47422 guest   60r   REG                1,8        402 36951947 /private/var/folders/jt/gf6q8xn50szb_fbqq1j36yzh0000gn/T/sh-thd.fQezbf
bash    47422 guest  255r   REG                1,8        402 36951899 /home/guest/workspace/bash/_exec.sh
1
2
3
4
5
COMMAND   PID       USER   FD   TYPE             DEVICE   SIZE/OFF     NODE NAME
bash    47422 guest  cwd    DIR                1,8       2312 32874335 /home/guest/workspace/bash
bash    47422 guest  txt    REG                1,8     945256 12433654 /usr/local/Cellar/bash/4.4.23/bin/bash
bash    47422 guest  txt    REG                1,8     841456 32850597 /usr/lib/dyld
bash    47422 guest  txt    REG                1,8 1175449600 32853756 /private/var/db/dyld/dyld_shared_cache_x86_64
bash    47422 guest    0r   REG                1,8        402 36951947 /private/var/folders/jt/gf6q8xn50szb_fbqq1j36yzh0000gn/T/sh-thd.fQezbf
bash    47422 guest    1   PIPE 0xcc5656065833aed5      16384          ->0xcc5656065833ae15
bash    47422 guest    2   PIPE 0xcc5656065833ab15      16384          ->0xcc5656065833af95
bash    47422 guest  255r   REG                1,8        402 36951899 /home/guest/workspace/bash/_exec.sh

その他サンプル

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

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

# ++++++++++++++++++++++ [TEST-2 Commands] ++++++++++++++++++++++
# コマンドキャプチャする

# 標準出力とエラー出力を複製する
exec 10>&1 11>&2

# 標準出力とエラー出力をそれぞれファイルにリダイレクトする
exec > stdout.out 2> stderr.out

# 以降の出力は全てファイルに書かれる
echo "hogehoge"
echo "foofoo" >&2

# 記述子10と11を閉じる
exec 1>&10- 2>&11-

# stdout.out
cat stdout.out
# stderr.out
cat stderr.out


# ******** [TEST-2 Output] ********
hogehoge
foofoo

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

  1. # copy stdin to descriptor 3
  2. exec 3<&0
  3.  
  4. # redirect stdin to test.c
  5. exec < test.c
  6.  
  7. # read a line from test.c
  8. read line
  9. echo $line
  10.  
  11. # restore stdin and close descriptor 3
  12. exec 0<&3 3<&-

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

  1. # save stdout as descriptor 3
  2. exec 3>&1
  3.  
  4. exec > test.txt
  5.  
  6. echo "this message is written into the test.txt"
  7.  
  8. # restore stdout and close descriptor 3
  9. exec 1>&3 3>&-

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

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

:> jobs

# stdoutを63に複製
exec 63>&1

# 63をプロセス置換でpipeの入力にする
exec 63> >(
  echo "child pid: $BASHPID"
  while read line; do
    echo "$(date): received: $line" | tee "$line.log"
    touch "$line.done"
  done
)

i=0
declare -r MAX_COUNT=10
echo "parent pid: $BASHPID"
while read line; do
  if [ "$line" = "q" ] || [ "$line" = "quit" ]; then
    break
  fi
  # プロセス置換のコマンドに送る
  echo "$line" >&63
  i=1
  until [ -f "$line.done" ] || (( i > $MAX_COUNT )); do
    sleep 1
    ((i+=1))
  done
  if (( i>$MAX_COUNT )); then
    echo "exceeded $MAX_COUNT seconds"
  fi
done < <(tail -f jobs) # jobsファイルから次のジョブを受け取る

# stdoutを戻し63を閉じる
exec 1>&63-


# ---- output -----
parent pid: 86757
child pid: 86772
202011日 水曜日 232625秒 JST: received: job1
202011日 水曜日 232628秒 JST: received: job1
202011日 水曜日 232629秒 JST: received: job1

# ----- 別シェルから -----
echo "job1" > jobs
echo "job1" > jobs
echo "job1" > jobs

ファイルディスクリプタの移動

  1. #!/usr/bin/env bash
  2.  
  3. # 複合コマンド
  4. {
  5.   # stdout.txtに出力
  6.   echo "this message is written into stdout.txt"
  7.   # stderr.txtに出力
  8.   echo "this message is written into stderr.txt" >&2
  9. } > stdout.txt 2> stderr.txt

digitの記述子がnに複製された後閉じられる。

読み書き可能なファイルディスクリプタ

# 入力
[n]<&digit-
# 出力
[n]>&digit-
[n]<>word

コマンド実行

コマンドの展開

word(=command) word(args...) ...

最初のwordはコマンド名として受領され、残りはコマンド引数として渡される。

varname=value command [args...]

varnameは、commandの実行環境に加えられる。

varname=value

varname=valueは、現在の実行環境に作用する。

# ++++++++++++++++++++++ [TEST-3 Commands] ++++++++++++++++++++++
echo "hello" > file.txt
exec 10<>file.txt
read line <&10
echo "read: $line"
echo "world" >&10
# read line cat read "world"
cat file.txt


# ******** [TEST-3 Output] ********
read: hello
hello
world

コマンドの探索と実行

  1. コマンド名が/を含まない場合、関数にコマンド定義があるか探索
  2. 関数にマッチしない場合、組み込みコマンドを探索
  3. 関数でも組み込みコマンドでもなく/を含まない場合、$PATHから探索。Bashはコマンドのフルパスをhashを使ってセーブする。まずhashテーブルを探し、コマンドが見つからない場合は、$PATHのフルサーチが行なわれる。コマンドが見つからない場合は、command_not_found_handle関数が呼ばれる(定義されている場合)。command_not_found_handleが定義されていない場合、終了コード127で終了する。
  4. コマンド探索に成功、もしくはコマンド名が/を含む場合、異なる実行環境でプログラムを実行する。引数0はコマンド名に設定され、残りの引数はコマンドの引数として渡される。
  5. ファイルが実行可能な形式でなく、ディレクトリでない場合、シェルスクリプトとして実行される。
  6. コマンドが非同期に実行されていなければ、シェルは完了を待ち、終了コードを得る。
$ FOO=var echo "foo"
foo
$ echo $FOO

$ FOO=var
$ echo $FOO
var

コマンド実行環境

以下の実行環境を持つ。

異なるシェル実行環境において以下環境は引き継がれる。

# ++++++++++++++++++++++ [TEST-1 Commands] ++++++++++++++++++++++
# コマンドが見つからない場合呼ばれる
command_not_found_handle() {
  echo "not found: $@"
}

# コマンドを作成
cat > mycmd <<'STR'
#!/usr/bin/env bash
echo $0
STR
chmod +x mycmd

# 存在しないコマンド
# command_not_found_handleが呼ばれる
foo args1 args2
echo $?
unset command_not_found_handle
foo args1 args2
echo $?

# mycmdをPATHで探索可能にする
PATH=`pwd`:$PATH

# hashはまだ空
hash -t mycmd

# コマンド実行、hashに登録される
mycmd

# mycmdはhashで見つかる
hash -t mycmd

# hashから削除
hash -d mycmd
hash -t mycmd

# hashに登録
hash mycmd
hash -t mycmd


# ******** [TEST-1 Output] ********
# コマンドが見つからない場合呼ばれる
# コマンドを作成
# 存在しないコマンド
# command_not_found_handleが呼ばれる
not found: foo args1 args2
0
hoge.sh: 行 72: foo: コマンドが見つかりません
127
# mycmdをPATHで探索可能にする
# hashはまだ空
hoge.sh: 79 行: hash: mycmd: 見つかりません
# コマンド実行、hashに登録される
/home/guest/workspace/bash/mycmd
# mycmdはhashで見つかる
/home/guest/workspace/bash/mycmd
# hashから削除
hoge.sh: 89 行: hash: mycmd: 見つかりません
# hashに登録
/home/guest/workspace/bash/mycmd

環境の追加や削除

# ++++++++++++++++++++++ [TEST-1 Commands] ++++++++++++++++++++++
trap "echo trap:parent && exit 0" 0

(
  trap "echo trap:child && exit 0" 0
)

(
  echo "shell"
)

alias myls='ls -l'

cat <<'STR' > my.sh
#!/bin/bash
echo "$0: $PWD"
echo "$0: PARAM=$PARAM"
echo "$0: MYENV=$MYENV"
echo "$0: MYENV2=$MYENV2"
echo "$0: $(alias myls)"
shopt -p extglob
shopt -s extglob
shopt -p extglob
bash my-sub.sh
STR

cat <<'STR' > my-sub.sh
#!/bin/bash
echo "$0: $PWD"
echo "$0: PARAM=$PARAM"
echo "$0: MYENV=$MYENV"
echo "$0: MYENV2=$MYENV2"
echo "$0: $(alias myls)"
shopt -p extglob
STR
chmod +x my*.sh

MYENV=myenv
export MYENV2=myenv2
PARAM=my ./my.sh


# ******** [TEST-1 Output] ********
trap:child
shell
./my.sh: /home/guest/workspace/bash
./my.sh: PARAM=my
./my.sh: MYENV=
./my.sh: MYENV2=myenv2
./my.sh: line 6: alias: myls: not found
./my.sh: 
shopt -u extglob
shopt -s extglob
my-sub.sh: /home/guest/workspace/bash
my-sub.sh: PARAM=my
my-sub.sh: MYENV=
my-sub.sh: MYENV2=myenv2
my-sub.sh: 6 行: alias: myls: 見つかりません
my-sub.sh: 
shopt -u extglob
trap:parent

set -k

-kオプションが有効な場合、シェル変数定義の形式であれば、コマンド名の前に定義しなくともシェルパラメータとして解釈される。

# ++++++++++++++++++++++ [TEST-1 Commands] ++++++++++++++++++++++

cat <<'STR' > main.sh
#!/bin/bash
echo "$0: MYENV=$MYENV"
echo "$0: PGPORT=$PGPORT"
echo "$0: PGDATABASE=$PGDATABASE"
# 一部の環境を削除
export -n PGPORT
declare +x PGDATABASE
# DEBUGはシェルパラメータで以下実行環境でのみ引き継ぎ
DEBUG=1 ./sub.sh
STR

cat <<'STR' > sub.sh
#!/bin/bash
echo "$0: MYENV=$MYENV"
echo "$0: PGPORT=$PGPORT"
echo "$0: PGDATABASE=$PGDATABASE"
echo "$0: DEBUG=${DEBUG:-}"
STR

chmod +x {main,sub}.sh

# 環境定義
declare -x MYENV=myenv
declare -x PGDATABASE=postgres
export PGPORT=5432

# サブシェルで実行
./main.sh


# ******** [TEST-1 Output] ********
# 環境定義
# サブシェルで実行
./main.sh: MYENV=myenv
./main.sh: PGPORT=5432
./main.sh: PGDATABASE=postgres
# 一部の環境を削除
# DEBUGはシェルパラメータで以下実行環境でのみ引き継ぎ
./sub.sh: MYENV=myenv
./sub.sh: PGPORT=
./sub.sh: PGDATABASE=
./sub.sh: DEBUG=1

終了ステータス

コマンド実行の終了ステータスは、waitpidシステムコール(と類似の関数群)が返す値であり0〜255の値をとる。
シェルは、125以上の値を使用する。

0は正常終了を意味する。致命的なシグナル(N)でコマンドが終了すると、Bashは特別なエラーモードとして128+Nの値を使う。コマンドが見つからない場合は127。コマンドは見つかるが実行可能でない場合は126。

組み込みコマンドは、不正な使用法を示すために終了ステータス2を返す。

シグナル

Bashは対話モードでは、SIGTERMは無視し、SIGINTはハンドルされる。SIGQUITは無視する。ジョブ制御が有効な場合、BashはSIGTTIN、SIGTTOU、SIGTSTPを無視する。

Linux manより。POSIX規格については関連を参照のこと。

シグナル
SIGHUP1
SIGINT2
SIGQUIT3
SIGILL4
SIGTRAP5
SIGABRT6
SIGIOT6
SIGBUS7
SIGFPE8
SIGKILL9
SIGUSR110
SIGSEGV11
SIGUSR212
SIGPIPE13
SIGALRM14
SIGTERM15
SIGSTKFLT16
SIGCHLD17
SIGCONT18
SIGSTOP19
SIGTSTP20
SIGTSTP21
SIGTTOU22
SIGUR23
SIGXCPU24
SIGXFSZ25
SIGVTALRM26
SIGPROF27
SIGWINCH28
SIGIO29
SIGPWR30
SIGSYS31
SIGUNUSED31

関連

参考 From BSD Man

# ++++++++++++++++++++++ [TEST-1 Commands] ++++++++++++++++++++++

cat <<'STR' > main.sh
#!/bin/bash
echo "$0: MYENV=$MYENV"
echo "$0: PGPORT=$PGPORT"
echo "$0: PGDATABASE=$PGDATABASE"
STR

# コマンド実行文で、name=value形式であればシェルパラメータとして定義
set -k

#1 サブシェルで実行
PGDATABASE=postgres ./main.sh PGPORT=5432 MYENV=myenv

set +k

#2 サブシェルで実行
PGDATABASE=postgres ./main.sh PGPORT=5432 MYENV=myenv


# ******** [TEST-1 Output] ********
# コマンド実行文で、name=value形式であればシェルパラメータとして定義
#1 サブシェルで実行
./main.sh: MYENV=myenv
./main.sh: PGPORT=5432
./main.sh: PGDATABASE=postgres
#2 サブシェルで実行
./main.sh: MYENV=
./main.sh: PGPORT=
./main.sh: PGDATABASE=postgres

シェル組み込みコマンド

シェル変数

Bourne Shell変数

Bash変数

Bashの機能

ジョブ制御

コマンドライン編集

コマンド履歴

参考リンク


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