GNU 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
  • switch case文のようなコマンド。
  • ;; で条件区切りとなる。;&、;;& という指定もある。;& は、フォールスルーする。;;& は、次にマッチする条件があるまで辿り、マッチすればcommand-listを実行する。
  • patternは、パターンマッチングのルールによって処理される。
  • | で区切れば、ORでpatternを複数書くことができる。
  • どのpatternにもマッチしない場合は、終了ステータス0となり、マッチすれば、command-listの終了ステータスとなる。
# --- 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 ]]
  • [[ は組み込みコマンドである。[ と振る舞いが異なることに注意。
  • [[ と ]]; の間のexpressionでは展開の扱いが異なる。
    • 単語分割、ファイル名展開は行われない。
    • チルダ展開、パラメータ・変数展開、算出式展開、コマンド置換、プロセス置換、クオート除去は作用する。
  • >、< は、現在のlocaleで作用する。
  • ==、!= は、右辺がパターンマーチングのルールで判定される。extglob オプションが有効な場合、= は、 == と等しい。nocasematch オプションが有効な場合、アルファベットの文字大小を無視する。終了ステータスは、== でマッチ、!= でマッチしないを満たす場合は0、そう出ない場合は1。
  • =~ は、右辺の文字列をPOSIX拡張正規表現として動作する。正規表現構文が正しくない場合、終了ステータスは2となる。グルーピングにマッチした文字列は、BASH_REMATCH配列に保存される。
# --- 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を評価しない。

  • expression1 && expression2
    • expression1がfalse、expression2は評価しない、結果はfalse
  • expression1 || expression2
    • expression1がtrue、expression2は評価しない、結果はtrue
(( 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]
  • サブシェルで非同期にコマンド実行することができる。&を使ったバックグラウンド実行と似ている。coprocで実行されたシェルと実行元のシェル間にはstdinとstdoutのパイプが作られる。pipeを呼び、forkしている。
  • コマンドのstdoutは NAME[0]、stdinは NAME[1]
# --- 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 ]
  • 関数の終了ステータスは、関数内の最後のコマンドの終了ステータスとなる。return [終了ステータス]で関数を抜ける。終了ステータスが指定されない場合は、returnの前にコマンドの終了ステータスとなる。
  • 引数は、$1〜の様な位置パラメータや$@$*で参照可能である。関数を終了すると、これらの値は関数実行前の状態にリストアされる(関数内でshiftしたりしても、関数を抜けると呼び出し前の状態である)。
  • FUNCNAMEは、実行中の関数名が入っている。FUNCNAMEは、配列でネストした関数スタックを取得できる。
  • unset -f で定義済み関数を削除できる。
  • local指定で、関数ローカルな変数定義ができる。グローバルと同様な名前であっても、local指定していれば関数内ではlocalで指定した値を使用でき、グルーバルを汚染しない。変数の可視性は動的スコープ。
  • declare -f、typeset -fで関数定義を確認できる。-Fオプションの場合は名前のみを表示する。
# --- 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が指定されない場合は、null
  • チルダ展開、変数展開、コマンド置換、算術展開、クオートremovalが行われる。単語分割、ファイル名展開は行われない。
  • 整数が渡される場合は、数値として評価される

+=

+=は、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

位置パラメータ

  • $N、${N}で引数で渡される値を取得できる。Nがシングルな数値でない場合はブレース{}がいる。
  • $0は、実行されたファイル名を示す。
  • setやshiftでset、unsetできる。
  • 関数実行時には、一時的に位置パラメータは置き換えられる。
  • $#は位置パラメータの数を示す。
  • $- は、setのオプションを示す。
# --- 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ならば、区切り文字なしで結合される。

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

$@

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

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

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

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

$#

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

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

$?

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

echo $USER #=> "moritetu"

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

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

$-

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

# ${parameter:=word}

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

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

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

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

$$

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

# ${parameter:+word}

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

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

$!

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

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

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

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

$0

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

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

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

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

array=(0 1 2 3 4 5)             #=> 
echo ${#array}                  #=> 1
echo ${#array[@]}               #=> 6
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

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

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

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

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

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

配列の操作

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

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

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

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

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

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

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

連想配列の操作

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

展開

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

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

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

チルダ展開

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

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

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

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

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

パラメータ展開

${parameter}

parameterの値に置換される。

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

${parameter:-word}

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

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

  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

${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 を展開したものが標準エラー出力に出力される。
  • wordがなければ パラメータが空文字列または設定されていないことを示すメッセージが標準エラー出力に出力される。
  • 対話的シェルでなければ、 シェルは終了する。
  • parameterに空文字列以外が設定されていれば、 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} の逆。

# ${parameter:+word}

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

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

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

  • 部分文字列の展開を行なう。
  • length指定がある場合は、parameter を展開したものから最大 length 文字を取り出す。length指定がない場合は、指定したインデックスから末尾までの文字を取り出す。
  • 配列の場合は、指定したインデックスから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に対しwordで前方一致するパターンを取り除いた結果を返す。#は最短一致、##は最長一致のパターン。
  • parameterが@や*の配列変数の場合、全ての要素に対して順番に適用される。
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に対しwordで後方一致するパターンを取り除いた結果を返す。%は最短一致、%%は最長一致のパターン。
  • parameterが@や*の配列変数の場合、全ての要素に対して順番に適用される。
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のpatternの最長一致する部分をstringに置換する。
  • / で始まる場合は、patternにマッチした全てが置換される。
  • # で始まる場合は、parameterを展開した値の先頭にマッチ。
  • %で始まる場合は、parameterを展開した値の末尾にマッチ。
  • parameterが、@や*の場合は、全ての要素に適用される。
parameter="# this is a comment line."  #=> 
array=("# this is a comment line." "# this is not a comment line.")  #=> 

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

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

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

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

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

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

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

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

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

参考 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

関連 算術式

プロセス置換

  • >(list)は、ファイルへの出力がlistへの入力となる。
  • <(list)は、listの出力がファイルへの入力となる。

サンプル 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 extglob

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

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

?(pattern-list)

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

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

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

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

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

# +++++++++++++++ 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
!(pattern-list)

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

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

リダイレクション

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

入力のリダイレクト

[n]<word

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

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

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

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

出力のリダイレクト

[n]>[|]word

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

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

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

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

[n]>>word

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

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

ヒアドキュメント

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

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

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

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

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

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

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

ヒアストリング

[n]<<< word

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

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

# 入力
[n]<&word
# 出力
[n]>&word
  • wordが数値ならば、記述子nはwordのコピーになる。
  • wordがオープンされた記述子でない場合はエラーとなる。
  • word - の場合、記述子nはクローズされる。

標準入力の複製

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

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

標準出力の複製

# ++++++++++++++++++++++ [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コマンドでリダイレクトだけを指定すると、シェル自身のファイル記述子とデバイスの対応を変更することができる。

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

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

  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

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

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

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

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

[n]<>word
# ++++++++++++++++++++++ [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

コマンド実行

コマンドの展開

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

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

varname=value command [args...]

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

varname=value

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

$ FOO=var echo "foo"
foo
$ echo $FOO

$ FOO=var
$ echo $FOO
var

コマンドの探索と実行

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

コマンド実行環境

以下の実行環境を持つ。

  • オープンされたファイル、execコマンドで実行されたリダイレクト
  • cd、pushd、popdの作業ディレクトリ環境
  • umaskによるマスク
  • キャッチされるtrapはリセットされ、無視されるトラップは無視される
  • シェル変数
  • 関数
  • オプション
  • shopt
  • aliasによるエイリアス
  • プロセスID(backgroundジョブを含む)、$$、$PPID

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

  • オープンされたファイル、リダイレクション
  • 作業ディレクトリ
  • exportされた変数や関数、環境変数
  • キャッチされるtrapはリセットされ、無視されるトラップは無視される
# ++++++++++++++++++++++ [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

環境の追加や削除

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

set -k

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

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

終了ステータス

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

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

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

シグナル

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

シグナルハンドラは親シェルから継承する。ジョブ制御が無効のとき、非同期なコマンドはSIGINT、SIGQUITを無視する。コマンド置換で実行されるコマンドは、キーボードから送られるシグナルSIGTTIN、SIGTTOU、SIGTSTPを無視する。

シェルは、SIGHUPで終了する。終了する前に対話シェルは全てのジョブにSIGHUPを送る。停止済みのジョブには、SIGHUPを受け取るのを保証するためにSIGCONTが送られる。シェルがSIGHUPを配送するのを防ぐには、disownコマンドでジョブテーブルから削除する、もしくはdisown -hでSIGHUPを受け取らないようマークする。

huponexitオプションがセットされている場合、対話ログインシェルが終了するとき、BashはSIGHUPを全てのジョブに配送する。

Bashは、コマンドが完了するのを待っており、トラップがセットされたシグナルを受け取った場合、トラップはコマンドが完了するまで実行されない。Bashは、組み込みのwaitを非同期コマンドを待っている時、トラップがセットされたシグナルを受け取ると、waitから128より大きな終了コードで直ちにコマンドから復帰しトラップが実行される。

waitで待つ間にシグナルを送る

# ++++++++++++++++++++++ [TEST-1 Commands] ++++++++++++++++++++++
function handler() {
  local status=$?
  echo "trapped($$): $status"
  exit $status
}

trap handler SIGINT
trap handler SIGTERM

cat <<EO > sigchild.sh
#!/bin/bash
sleep 5
echo "send SIGINT to $$"
kill -s SIGINT $$
sleep 10
EO

echo "run sigchild.sh"
bash sigchild.sh &

wait $!

exit 0


# ******** [TEST-1 Output] ********
run sigchild.sh
send SIGINT to 27488
trapped(27488): 130

バックグラウンドでジョブ実行(huponexit on/off)

#
# huponexit on
#
$ shopt -s huponexit
$ sleep 120 &
$ exit

# 再度ログイン
$ ps aux | grep [s]leep
# いない

#
# huponexit off
#
$ shopt -u huponexit
$ sleep 120 &
$ exit

# 再度ログイン
$ ps aux | grep [s]leep
# いる

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

     No    Name         Default Action       Description
     1     SIGHUP       terminate process    terminal line hangup
     2     SIGINT       terminate process    interrupt program
     3     SIGQUIT      create core image    quit program
     4     SIGILL       create core image    illegal instruction
     5     SIGTRAP      create core image    trace trap
     6     SIGABRT      create core image    abort program (formerly SIGIOT)
     7     SIGEMT       create core image    emulate instruction executed
     8     SIGFPE       create core image    floating-point exception
     9     SIGKILL      terminate process    kill program
     10    SIGBUS       create core image    bus error
     11    SIGSEGV      create core image    segmentation violation
     12    SIGSYS       create core image    non-existent system call invoked
     13    SIGPIPE      terminate process    write on a pipe with no reader
     14    SIGALRM      terminate process    real-time timer expired
     15    SIGTERM      terminate process    software termination signal
     16    SIGURG       discard signal       urgent condition present on socket
     17    SIGSTOP      stop process         stop (cannot be caught or ignored)
     18    SIGTSTP      stop process         stop signal generated from keyboard
     19    SIGCONT      discard signal       continue after stop
     20    SIGCHLD      discard signal       child status has changed
     21    SIGTTIN      stop process         background read attempted from control terminal
     22    SIGTTOU      stop process         background write attempted to control terminal
     23    SIGIO        discard signal       I/O is possible on a descriptor (see fcntl(2))
     24    SIGXCPU      terminate process    cpu time limit exceeded (see setrlimit(2))
     25    SIGXFSZ      terminate process    file size limit exceeded (see setrlimit(2))
     26    SIGVTALRM    terminate process    virtual time alarm (see setitimer(2))
     27    SIGPROF      terminate process    profiling timer alarm (see setitimer(2))
     28    SIGWINCH     discard signal       Window size change
     29    SIGINFO      discard signal       status request from keyboard
     30    SIGUSR1      terminate process    User defined signal 1
     31    SIGUSR2      terminate process    User defined signal 2

シェル組み込みコマンド

Bourne Shell組み込みコマンド

: (コロン)

: [arguments]

引数の展開やリダイレクト以外に何もしない。終了ステータスは0。

# 何も実行されない
$ : [Comment]
$ : echo "hello"
$ echo "foo" > foo
$ cat foo
foo
# Truncate
$ :>foo
$ cat foo 
: 

# 変数初期化
# 以下と同様、DEBUGと2回書かなくても良い
# DEBUG=${DEBUG:-2}
$ : ${DEBUG:=2}
$ echo $DEBUG
2

. (ピリオド)

. filename [arguments]

現在のシェルコンテキストでファイルを読み実行する。sourceと等しい。最後のコマンドの終了コードが戻される。

-Tオプションがセットされている場合、DEBUGトラップを継承する。

#
# file _exec.sh
#
$ cat _exec.sh 
trap 'read -p "$0($LINENO) $BASH_COMMAND"' DEBUG
cat <<EO > debug-child.sh
#!/bin/bash
pwd
echo "hello"
echo "world"
EO

set -T

source debug-child.sh

exit 0


#
# set +T
#
$ bash _exec.sh 
_exec.sh(2) cat  > debug-child.sh <<EO
#!/bin/bash
pwd
echo "hello"
echo "world"
EO

_exec.sh(9) set +T
_exec.sh(11) source debug-child.sh
/Users/guest/workspace/bash
hello
world
_exec.sh(13) exit 0

#
# set -T
#
$ bash _exec.sh 
_exec.sh(2) cat  > debug-child.sh <<EO
#!/bin/bash
pwd
echo "hello"
echo "world"
EO

_exec.sh(9) set -T
_exec.sh(11) source debug-child.sh
_exec.sh(2) pwd
/Users/guest/workspace/bash
_exec.sh(3) echo "hello"
hello
_exec.sh(4) echo "world"
world
_exec.sh(13) exit 0

break

break [n]

forwhileuntilselectループを抜ける。n(1以上)が指定されると、nのレベルのループを抜ける。終了ステータスは0。

# ++++++++++++++++++++++ [TEST-2 Commands] ++++++++++++++++++++++
i=0
j=0

while (( i < 10 )); do
  echo i=$i
  for j in {1..20}; do
    if [ $j -gt 5 ]; then
      # whileを抜ける
      break 2
    fi
    echo j=$j
  done
  ((i++))
done


# ******** [TEST-2 Output] ********
i=0
j=1
j=2
j=3
j=4
j=5

cd

cd [-L|[-P [-e]] [-@] [directory]

ディレクトリを変更する。directoryが指定されない場合は、HOMEに移動する。directoryに続き引数は無視される。CDPATHが存在する場合は、検索対象のパスとして使われる。CDPATHは、: で区切られる。/からdirectoryが始まる場合は、CDPATHは使用されない。

-Pオプションは、シンボリックリンクを解決する(シンボリックリンクの指す先のディレクトリ名になる)。

..directoryに現れると、..の前のパスを取り除く。

-eオプションが-Pと一緒に指定され、ディレクトリ変更後に現在のワーキングディレクトリが正しく決定できない場合、cdは失敗のステータスを返す。

directory- ならばOLDPWDに置き換えられる。

最初の引数が-の場合、ディレクトリ変更に成功するとその絶対パスが標準出力に書かれる。

シンボリックリンクの解決

vagrant@localhost foo]$ ls -la
合計 0
drwxrwxr-x. 3 vagrant vagrant  29  326 05:28 .
drwx------. 4 vagrant vagrant 137  326 05:24 ..
lrwxrwxrwx. 1 vagrant vagrant   3  326 05:28 link -> sym
drwxrwxr-x. 2 vagrant vagrant  18  326 05:28 sym
[vagrant@localhost foo]$ pwd
/home/vagrant/foo
[vagrant@localhost foo]$ cd -P link
[vagrant@localhost sym]$ pwd
/home/vagrant/foo/sym
[vagrant@localhost sym]$ cd ..
[vagrant@localhost foo]$ ll
合計 0
lrwxrwxrwx. 1 vagrant vagrant  3  326 05:28 link -> sym
drwxrwxr-x. 2 vagrant vagrant 18  326 05:28 sym
[vagrant@localhost foo]$ cd -L link
[vagrant@localhost link]$ pwd
/home/vagrant/foo/link

絶対パスの表示

[vagrant@localhost ~]$ ls
foo  test.sh
[vagrant@localhost ~]$ cd foo
[vagrant@localhost foo]$ cd
[vagrant@localhost ~]$ cd - foo
/home/vagrant/foo

continue

continue [n]

for、while、until、selectなどのループで次のイテレーションにいく。nが指定されると、n番目までの実行が再開される。

以下、breakとの違いを確認しておく。

# ++++++++++++++++++++++ [TEST-1 Commands] ++++++++++++++++++++++
i=0 j=0 k=0

while test $k -lt 5; do
  echo "k = $k"
  while test $i -lt 3; do #1
    echo "  i = $i"
    ((i++))
    while test $j -lt 5; do
      echo "    j = $j"
      ((j++))
      if [ $j -gt 2 ]; then
        echo "#3に移る"
        break 2
      fi
    done
    echo "#2"
  done
  echo "#3"
  ((k++))
done


# ******** [TEST-1 Output] ********
k = 0
  i = 0
    j = 0
    j = 1
    j = 2
#3に移る
#3
k = 1
  i = 1
    j = 3
#3に移る
#3
k = 2
  i = 2
    j = 4
#3に移る
#3
k = 3
#3
k = 4
#3


# ++++++++++++++++++++++ [TEST-2 Commands] ++++++++++++++++++++++
i=0 j=0 k=0

while test $k -lt 5; do
  echo "k = $k"
  while test $i -lt 3; do #1
    echo "  i = $i"
    ((i++))
    while test $j -lt 5; do
      echo "    j = $j"
      ((j++))
      if [ $j -gt 2 ]; then
        echo "#1に移る"
        continue 2
      fi
    done
    echo "#2"
  done
  echo "#3"
  ((k++))
done


# ******** [TEST-2 Output] ********
k = 0
  i = 0
    j = 0
    j = 1
    j = 2
#1に移る
  i = 1
    j = 3
#1に移る
  i = 2
    j = 4
#1に移る
#3
k = 1
#3
k = 2
#3
k = 3
#3
k = 4
#3

eval

eval [arguments]

argumentsをコマンドとして評価する。終了ステータスは、コマンド実行の結果となる。argumentsが指定されていないまたは空の場合は、終了ステータスは0となる。

# ++++++++++++++++++++++ [TEST-1 Commands] ++++++++++++++++++++++
#1
eval 'echo $HOME'
#2
eval 'echo $((1+1))'

#3
pwd
a=pwd; varname=a; b="\$${varname}";
eval "${b}"


# ******** [TEST-1 Output] ********
#1
/home/guest
#2
2
#3
/home/guest/workspace/bash
/home/guest/workspace/bash
# ++++++++++++++++++++++ [TEST-2 Commands] ++++++++++++++++++++++
a= b=99

cat <<STR > com.txt
a=10 && echo \$a
b=20 && echo \$b
echo \$((a+b))
c=100
STR

i=1
while read line; do
  echo "LINE[$i]: $line"
  eval "$line"
  ((i++))
done < com.txt
echo "a=$a b=$b c=$c"


# ******** [TEST-2 Output] ********
LINE[1]: a=10 && echo $a
10
LINE[2]: b=20 && echo $b
20
LINE[3]: echo $((a+b))
30
LINE[4]: c=100
a=10 b=20 c=100

exec

exec [-cl] [-a name] [command [arguments]]

新規プロセスを作らず現在のシェルを置き換える。-lオプションは、コマンドに渡される0番目の引数の最初にダッシュを置く。loginプログラムが行なっている。-cオプションは、環境を引き継がずコマンド実行を行なう。-aオプションは、0番目の引数としてnameを渡す。コマンド実行できない場合、execfailオプションが有効でなければ非対話シェルは終了する。対話シェルはファイルが実行されないならば失敗コードを返す。サブシェルは、execに失敗すると無条件で終了する。commandが指定されない場合、リダイレクトは現在のシェル環境に作用する。もしリダイレクトがエラーでなければ、0が返る。

exec

# ++++++++++++++++++++++ [TEST-1 Commands] ++++++++++++++++++++++
export MYENV=MYENV

cat <<'EOF' > _exec.sh
#!/bin/bash
echo $_
echo $0
echo $@
echo $MYENV
EOF

exec bash _exec.sh arg1


# ******** [TEST-1 Output] ********
bash
_exec.sh
arg1
MYENV

exec -c -a

 ++++++++++++++++++++++ [TEST-1 Commands] ++++++++++++++++++++++
export MYENV=MYENV

cat <<'EOF' > _exec.sh
#!/bin/bash
echo $_
echo $0
echo $@
echo $MYENV
EOF

exec -c -a myprogram bash _exec.sh arg1


# ******** [TEST-1 Output] ********
myprogram
_exec.sh
arg1
 

サンプル execとリダイレクト

# ++++++++++++++++++++++ [TEST-1 Commands] ++++++++++++++++++++++
# fd20のディスクリプタを開き1に複製
exec 20>&1
# fd1をファイルに向ける
exec > stdout.out
echo "foo"
echo "bar"
# fd20を閉じ1を復元
exec >&20-

cat stdout.out


# ******** [TEST-1 Output] ********
foo
bar

exit

exit [n]

シェルを終了し終了コードnを返す。終了ステータスが指定されない場合、最後に実行されたコマンドの終了ステータスとなる。
EXITトラップは、シェルが終了する前に実行される。

exitとEXITトラップの例

# ++++++++++++++++++++++ [TEST-1 Commands] ++++++++++++++++++++++
# ハンドラを設定
trap 'exit_handler' EXIT

function exit_handler() {
  local status=$?

  echo "called exit_handler: $status"
  return $status
}


# あえてFailureとなるコマンドを実行
test "" = "1"  # failure
# exit 0
exit  # exit status 1


# ******** [TEST-1 Output] ********
called exit_handler: 1

EXITトラップと複数のハンドラを設定

# ++++++++++++++++++++++ [TEST-1 Commands] ++++++++++++++++++++++
# ハンドラ配列
declare -a exit_handlers=()

# EXITトラップ
trap 'invoke_exit_handlers' EXIT

# ハンドラの実行
invoke_exit_handlers() {
  local exit_status=$?
  for h in ${exit_handlers[@]}; do
    $h $exit_status
    if [ $? -ne 0 ]; then
      : do something
    fi
  done
}

# ハンドラの追加
add_exit_handler() {
  exit_handlers[${#exit_handlers[@]}]=$1
}

# ハンドラ1
function handler1() {
  local status=$1

  echo "Called ${FUNCNAME[0]}: $status"
  return $status
}

# ハンドラ2
function handler2() {
  local status=$1

  echo "Called ${FUNCNAME[0]}: $status"
  return $status
}


# ハンドラを追加
add_exit_handler handler1
add_exit_handler handler2


# あえてFailureとなるコマンドを実行
test "" = "1"  # failure
# exit 0
exit  # exit status 1


# ******** [TEST-1 Output] ********
Called handler1: 1
Called handler2: 1

export

export [-fn] [-p] [name[=value]]

nameの環境情報が子プロセスに引き継がれるようにする。-fオプションは、nameがシェル関数であることを示す。-nオプションは、nameを引き継がないようにする。nameや-pオプションが指定された場合、exportされる変数リストが表示される。-pオプションは、入力としての再利用可能な形式での出力を行なう。name=valueとなる場合は、valueが設定される。

## ++++++++++++++++++++++ [TEST-1 Commands] ++++++++++++++++++++++
cat <<'STR' > _export.sh
#!/bin/bash
echo "MYENV: $MYENV"

echo "call my_func"
myfunc
STR

# MYENVを引き継ぐ
export MYENV=myenv

myfunc() {
  echo ${FUNCNAME};
}
# myfuncを引き継ぐ
export -f myfunc

bash _export.sh

# export
export -p


# ******** [TEST-1 Output] ********
MYENV: myenv
call my_func
myfunc
declare -x HOME="/home/guest"
declare -x LANG="ja_JP.UTF-8"
declare -x LC_ALL="ja_JP.UTF-8"
declare -x LOGNAME="guest"
declare -x MYENV="myenv"
declare -x OLDPWD="/home/guest"
# 長いので省略...

getopts

getopts optstring name [args]

オプソション解析で使われるコマンド。optstringにコロン(:)が含まれる場合は、スペース区切りで値を持つことを示す。: と ? は、オプション文字列として使用できない。getoptsが呼ばれる度に、nameに次のオプションの値を設定する。OPTINDに次の変数のインデックスが代入される。OPTINDは、シェル、シェルスクリプトが呼び出されると1に初期化される。オプション値はOPTARGに置かれる。getoptsを同じシェル内で繰り返し使用する場合、シェルはOPTINDをリセットしないので、手動でリセットが必要である。

オプションの終わりになると、getoptsは0以上の終了ステータスを返す。OPTINDは、最初のオプションでない引数のインデックスにセットされ、nameは?にセットされる。

getoptsは通常位置パラメータをパースする。

optstringの最初の文字が:の場合、サイレントエラーとなる。OPTERRが0の場合、エラーメッセージは表示されない。

無効なオプションが含まれていた場合、getoptsはnameに?を設定し、サイレントエラーでなければエラーメッセージを表示し、OPTARGをunsetとする。サイレントの場合、見つかったオプション文字がOPTARGに置かれ、診断メッセージは表示されない。

# ++++++++++++++++++++++ [TEST-1 Commands] ++++++++++++++++++++++
#1 OPTERR
echo "#1 OPTERR: $OPTERR"

#2 オプションのパース
# [options]
#   -a <arg>
#   -b <arg>
#   -c
#   -D
# No silent mode
while getopts "a:b:cD" optname; do
  case $optname in
    a)
      echo "-a: $OPTARG"
      ;;
    b)
      echo "-b: $OPTARG"
      ;;
    c|D)
      echo "Found $optname option"
      ;;
  esac
done

# オプション解析後の位置
echo $OPTIND
# 位置パラメータを最初の引数へセット
shift $((OPTIND-1))
# 残りの引数
echo $@


# ******** [TEST-1 Output] ********
$ bash getopts.sh -a a -b b -c -D -f arg1
#1 OPTERR: 1
-a: a
-b: b
Found c option
Found D option
_exec.sh: 不正なオプションです -- f
#3 OPTIND: 8
#5: arg1

hash

hash [-r] [-p filename] [-dt] [name]

pwd

pwd [-LP]

現在のワーキングディレクトリのパスを表示する。-Pはシンボリックリンクを含まないパスを表示する。-Lはシンボリックリンクを含んだパス名を返す。

$ pwd
/Users/guest/workspace/bash
$ echo $PWD
/Users/guest/workspace/bash

readonly

readonly [-aAf] [-p] [name[=value]]

nameをリードオンリーにする。-fオプションは、関数への参照を示す。-aオプションは、配列への参照を示す。-Aオプションは、連想配列への参照を示す。nameがないまたは-pオプションが指定される場合は、readonlyの全てのリストが表示される。-pオプションは、入力の再利用可能な形式で出力する。valueが指定されていれば、nameにセットされる。

# ++++++++++++++++++++++ [TEST-1 Commands] ++++++++++++++++++++++
declare -A map=()
declare -a array=(1 2 3)

myfunc() {
  echo $FUNCNAME
}
# 関数を読み取り専用にする
readonly -f myfunc

myfunc() {
  echo "FUNCNAME: $FUNCNAME"
}

# 読み取り専用のmapを定義する
readonly -A ro_map=([a]=1)

# 再利用可能な形式で出力
readonly -p

# 変更不可
ro_map=([b]=2)

# 変更可能
array[1]=11


# ******** [TEST-1 Output] ********
# 関数を読み取り専用にする
hoge.sh: 行 67: myfunc: 読み取り専用関数です
# 読み取り専用のmapを定義する
# 再利用可能な形式で出力
declare -r BASHOPTS="cmdhist:complete_fullquote:extquote:force_fignore:hostcomplete:interactive_comments:progcomp:promptvars:sourcepath"
declare -ir BASHPID
declare -ar BASH_VERSINFO=([0]="4" [1]="4" [2]="23" [3]="1" [4]="release" [5]="x86_64-apple-darwin17.5.0")
declare -ir EUID="501"
declare -ir PPID="42776"
declare -r SHELLOPTS="braceexpand:hashall:interactive-comments"
declare -ir UID="501"
declare -Ar ro_map=([a]="1" )
# 変更不可
hoge.sh: 行 76: ro_map: 読み取り専用の変数です

return

return [n]

関数から戻り呼び出し元にnのステータスを返す。nが指定されない場合は、最後に実行されたコマンドの終了ステータスとなる。trapハンドラでreturnが使われる場合、trapハンドラの前に実行された最後のコマンドの終了ステータスとなる。DEBUGトラップでreturnが使われる場合、returnが呼ばれる前に実行されたコマンドの終了ステータスとなる。

EXITトラップ

# ++++++++++++++++++++++ [TEST-1 Commands] ++++++++++++++++++++++
trap 'my_handler' EXIT

my_handler() {
  echo "called_handler: $?"
  return 2
}

exit 1


# ******** [TEST-1 Output] ********
called_handler: 1

# 終了ステータス
$ echo "exit:$?"
1

DEBUGトラップ

# ++++++++++++++++++++++ [TEST-2 Commands] ++++++++++++++++++++++
trap 'my_handler' DEBUG

my_handler() {
  echo "$? ($LINENO) $BASH_COMMAND"
  return 2
}

[ "" != "" ]
echo $?
echo "world"


# ******** [TEST-2 Output] ********
0 (6) [ "" != "" ]
1 (6) echo $?
1
0 (6) echo "world"
world

# 終了ステータス
$ echo "exit:$?"
0

shift

shift [n]

位置パラメータを左シフトする。nの整数で$#以下の値である。nが0または$#より大きい場合は、変わらない。nが指定されていない場合は、nが1と同じとなる。終了ステータスは正常な場合は0、そうでない場合は0でない値となる。

# ++++++++++++++++++++++ [TEST-1 Commands] ++++++++++++++++++++++
_#() { echo "# $@"; }
#!/usr/bin/env bash

echo "argnum: $#"
echo "args: $@"

echo "arg[0]: $1"

usage() {
  echo  "Usage: $(basename "${BASH_SOURCE}") [OPTIONS] [arg..]"
}

_# "自前のオプション解析"
while [ $# -gt 0 ]; do
  case "$1" in
    -h|--help)
      usage && exit
      ;;
    -t)
      # オプション値を取得
      shift
      echo "-t: $1"
      ;;
    -*)
      echo "invalid parameter: $1" >&2
      ;;
    *)
      break
      ;;
  esac
  shift
done

_# "オプション解析後の引数情報"
echo "\$#: $#"
echo "\$@: $@"


# ******** [TEST-1 Output] ********
argnum: 3
args: -t 1 10
arg[0]: -t
# 自前のオプション解析
-t: 1
# オプション解析後の引数情報
$#: 1
$@: 10

test、[

test expr

条件式を評価し、0(true) or 1(false)を返す。testは、オプションを受け入れない、またオプションの終わりを示す -- も受け入れない。

[ は、 ]で終わる必要がある。

! expr

exprがfalseならばTrue

# 長さ0ではない(0なのでfalse)
$ test ! -z "" ; echo $? 
1
( expr )

exprの値を返す。演算子の評価順を上書きするのに使う。

$ [[ 4 -gt 2 && ( 1 -gt 2 || 4 -gt 2 ) ]] ; echo $?
0
expr1 -a expr2

ext1とexpr2がtrueならばTrue。

$ [ "1" = "1" -a "0" = "0" ]; echo $?
0
expr1 -o expr2

ext1またはexpr2のいずれかがtrueならばTrue。

$ [ "1" = "0" -o "0" = "0" ]; echo $?
0
0 arguments

式はfalse

$ test ; echo $?
1
1 arguments

式がnullでなければtrue。

$ test 1; echo $?
0
$ test ""; echo $?
1
2 arguments

最初の引数が ! で2番目がnullならばtrue。最初の引数が単項演算子で、テスト結果がtrueならばtrue。

$ test ! "" ; echo $?
0
$ test ! 1 ; echo $?
1
3 arguments

以下の条件が順に適用される。

  1. 2番目の引数が二項演算子ならば、式の結果は1番目と3番目のオペランドを使った二項演算の結果となる。
  2. 最初の引数が ! ならば、2番目と3番目の引数を使ったテスト結果の否定となる。
  3. 最初の引数が ( で3番目が ) ならば、結果は2番目の1つの引数の結果となる。
  4. そうでない場合、式はfalse。
$ test ! -z "" ; echo $?
1
4 arguments

最初の引数が ! ならば、残りの引数で構成される3つの引数をとる式の否定。そうでない場合、式はパースされ上で述べたルールで評価される。

$ [[ ! "a" > "b" ]]; echo $?
0
5 arguments

式はパースされ、上で述べたルールにそって評価される。

times

times

コマンド実行にかかったユーザ、システム時間を表示する。

$ times ls
0m0.268s 0m0.121s
0m0.192s 0m0.193s

trap

trap [-lp] [arg] [sigspec …]

シグナルを捕捉するハンドラを定義する。argが、 未指定または- の場合は、シェルが呼ばれたときの値にリセットされる。argがnull文字の場合、sigspecで指定されているシグナルは無視される。argがなく-pが指定されている場合、sigspecに結びつくトラップコマンドが表示される。引数なしで-pのみが指定された場合、入力の再利用可能な形式でトラップコマンドリストが出力される。-lは、シグナルとシグナルの番号が表示される。sigspecは、シグナル名がシグナル番号を指定する。シグナル名は大文字小文字を区別せず、SIGプリフィクスは任意。

sigspecが0またはEXITの時、シェルが終了するときにargが実行される。sigspecがDEBUGのとき、各コマンド実行の前にargが実行される。sigspecがRETURNのとき、シェル関数やスクリプトが .sourceで実行されるスクリプトが実行を終える度にargのコマンドが実行される。

sigspecがERRのとき、パイプライン、リスト、複合コマンドが非0で終了するとargが実行される。

$ trap -p
trap -- 'shell_session_update' EXIT
$ trap -l
 1) SIGHUP	 2) SIGINT	 3) SIGQUIT	 4) SIGILL	 5) SIGTRAP
 6) SIGABRT	 7) SIGEMT	 8) SIGFPE	 9) SIGKILL	10) SIGBUS
11) SIGSEGV	12) SIGSYS	13) SIGPIPE	14) SIGALRM	15) SIGTERM
16) SIGURG	17) SIGSTOP	18) SIGTSTP	19) SIGCONT	20) SIGCHLD
21) SIGTTIN	22) SIGTTOU	23) SIGIO	24) SIGXCPU	25) SIGXFSZ
26) SIGVTALRM	27) SIGPROF	28) SIGWINCH	29) SIGINFO	30) SIGUSR1
31) SIGUSR2	

DEBUGトラップ

# ++++++++++++++++++++++ [TEST-1 Commands] ++++++++++++++++++++++
trap 'read -p "$0:$LINENO: $BASH_COMMAND"' DEBUG

echo "hello"
echo "world"

trap - DEBUG
echo "hello"
echo "world"


# ******** [TEST-1 Output] ********
$ bash _exec.sh 
_exec.sh:4: echo "hello"
hello
_exec.sh:5: echo "world"
world
_exec.sh:7: trap - DEBUG
hello
world

RETURNトラップ

実行した環境では、関数にtrapが引き継がれておらず、functrace or extdebugオプションを有効化することでreturnをトラップしている。

# ++++++++++++++++++++++ [TEST-2 Commands] ++++++++++++++++++++++
_#() { echo "# $@"; }
trap 'echo "@trap return"' RETURN

cat <<'STR' > include.sh
echo "hello"
STR

function myfunc() {
  echo "myfunc"
}

function include_file() {
  . include.sh
}

_# "my"
myfunc

_# "source"
source include.sh

_# "include_file"
include_file

_# "include_file with"
_# "functrace or extdebug"
_# "set -o functrace"
shopt -s extdebug
include_file


# ******** [TEST-2 Output] ********
# my
myfunc
# source
hello
@trap return
# include_file
hello
# include_file with
# functrace or extdebug
# set -o functrace
hello
@trap return
@trap return

参考 https://unix.stackexchange.com/questions/419017/return-trap-in-bash-not-executing-for-function/419045#419045

umask

umask [-p] [-S] [mode]

シェルのumaskをmodeに設定する。

# 入力可能なフォーマットでmode出力
$ umask -p
umask 0022

# シンボリック形式でmode出力
$ umask -S
u=rwx,g=rx,o=rx

# umaskを077に設定
$ umask 077
$ umask -S
u=rwx,g=,o=

unset

unset [-fnv] [name]

シェル変数や関数を削除する。-vオプションは、変数を参照する。-fオプションは、関数を参照する。-nオプションは、namerefを参照する。オプションが指定されなければ、変数を参照し、変数定義がなければ、関数が削除される。readonly属性の変数は削除されない。

# sample.sh
# ++++++++++++++++++++++ [TEST-1 Commands] ++++++++++++++++++++++
_#() { echo "# $@"; }
_# "変数"
noraml_variable="test"
_# "ハッシュ"
declare -A map=([a]=1)
_# "配列"
declare -a array=(1 2)

_# "関数"
function my() {
  echo "myfunc"
}

function my_2() {
  echo "myfunc2"
}

function var_dump() {
  echo "++++ var_dump +++"
  echo "noraml_variable: $noraml_variable"
  echo "map: ${map[@]}"
  echo "array: ${array[@]}"
  declare -F my
  declare -F my_2
  declare -p ref_var
  declare -p ref_func
}

_# "名前参照"
variable="variable"
declare -n ref_var=variable
declare -n ref_func=my_2

_# "変数情報の出力"
var_dump

unset noraml_variable
unset map
_# "インデックス0のみ削除"
unset array[0]
_# "関数削除"
unset -f my
_# "参照先の定義を削除"
unset ref_var
_# "参照名のみ削除"
unset -n ref_func

_# "変数情報の出力"
var_dump


# ******** [TEST-1 Output] ********
$ bash sample.sh 
# 変数
# ハッシュ
# 配列
# 関数
# 名前参照
# 変数情報の出力
++++ var_dump +++
noraml_variable: test
map: 1
array: 1 2
my
my_2
declare -n ref_var="variable"
declare -n ref_func="my_2"
# インデックス0のみ削除
# 関数削除
# 参照先の定義を削除
# 参照名のみ削除
# 変数情報の出力
++++ var_dump +++
noraml_variable: 
map: 
array: 2
my_2
declare -n ref_var="variable"
sample.sh: 26 行: declare: ref_func: 見つかりません

# 終了ステータス
$ echo $?
1

Bash組み込みコマンド

alias

alias [-p] [name[=value] …]

コマンドのエイリアスを設定する。-pオプションまたは引数なしで実行すると、再利用可能なフォーマットで現在定義されているエイリアス一覧を出力する。nameだけの場合は、nameとvalueの定義を出力する。

# エイリアスの定義一覧を表示する
$ alias
alias la='ls -la'
alias ll='ls -l'
alias lla='ls -la'
alias ls='ls -G'

# nameのみ指定
$ alias la
alias la='ls -la'

bind

bind [-m keymap] [-lpsvPSVX]
bind [-m keymap] [-q function] [-u function] [-r keyseq]
bind [-m keymap] -f filename
bind [-m keymap] -x keyseq:shell-command
bind [-m keymap] keyseq:function-name
bind [-m keymap] keyseq:readline-command

現在のReadlineのキーと関数バインドを表示および設定を行なう。

# Ctrl+]で単語単位でカーソルを前方に移動
$ bind '"\C-]": forward-word'

例:現在の関数とバインディングを表示する

$ bind -p

"\C-g": abort
"\C-x\C-g": abort
"\e\C-g": abort
"\C-j": accept-line
"\C-m": accept-line
# alias-expand-line (not bound)
# arrow-key-prefix (not bound)
# backward-byte (not bound)
"\C-b": backward-char
"\eOD": backward-char
"\e[D": backward-char
"\C-h": backward-delete-char
"\C-?": backward-delete-char
"\C-x\C-?": backward-kill-line
"\e\C-h": backward-kill-word
"\e\C-?": backward-kill-word
"\eb": backward-word
"\e<": beginning-of-history
"\C-a": beginning-of-line
"\eOH": beginning-of-line
"\e[H": beginning-of-line
"\e[200~": bracketed-paste-begin
"\C-xe": call-last-kbd-macro
"\ec": capitalize-word
"\C-]": character-search
"\e\C-]": character-search-backward
"\C-l": clear-screen
"\C-i": complete
"\e\e": complete
"\e!": complete-command
"\e/": complete-filename
"\e@": complete-hostname
"\e{": complete-into-braces
"\e~": complete-username
"\e$": complete-variable
# copy-backward-word (not bound)
# copy-forward-word (not bound)
# copy-region-as-kill (not bound)
# dabbrev-expand (not bound)
"\C-d": delete-char
"\e[3~": delete-char
# delete-char-or-list (not bound)
"\e\\": delete-horizontal-space
"\e-": digit-argument
"\e0": digit-argument
"\e1": digit-argument
"\e2": digit-argument
"\e3": digit-argument
"\e4": digit-argument
"\e5": digit-argument
"\e6": digit-argument
"\e7": digit-argument
"\e8": digit-argument
"\e9": digit-argument
"\C-x\C-v": display-shell-version
"\C-xA": do-lowercase-version
"\C-xB": do-lowercase-version
"\C-xC": do-lowercase-version
"\C-xD": do-lowercase-version
"\C-xE": do-lowercase-version
"\C-xF": do-lowercase-version
"\C-xG": do-lowercase-version
"\C-xH": do-lowercase-version
"\C-xI": do-lowercase-version
"\C-xJ": do-lowercase-version
"\C-xK": do-lowercase-version
"\C-xL": do-lowercase-version
"\C-xM": do-lowercase-version
"\C-xN": do-lowercase-version
"\C-xO": do-lowercase-version
"\C-xP": do-lowercase-version
"\C-xQ": do-lowercase-version
"\C-xR": do-lowercase-version
"\C-xS": do-lowercase-version
"\C-xT": do-lowercase-version
"\C-xU": do-lowercase-version
"\C-xV": do-lowercase-version
"\C-xW": do-lowercase-version
"\C-xX": do-lowercase-version
"\C-xY": do-lowercase-version
"\C-xZ": do-lowercase-version
"\eA": do-lowercase-version
"\eB": do-lowercase-version
"\eC": do-lowercase-version
"\eD": do-lowercase-version
"\eE": do-lowercase-version
"\eF": do-lowercase-version
"\eG": do-lowercase-version
"\eH": do-lowercase-version
"\eI": do-lowercase-version
"\eJ": do-lowercase-version
"\eK": do-lowercase-version
"\eL": do-lowercase-version
"\eM": do-lowercase-version
"\eN": do-lowercase-version
"\eP": do-lowercase-version
"\eQ": do-lowercase-version
"\eR": do-lowercase-version
"\eS": do-lowercase-version
"\eT": do-lowercase-version
"\eU": do-lowercase-version
"\eV": do-lowercase-version
"\eW": do-lowercase-version
"\eX": do-lowercase-version
"\eY": do-lowercase-version
"\eZ": do-lowercase-version
"\el": downcase-word
# dump-functions (not bound)
# dump-macros (not bound)
# dump-variables (not bound)
"\e\C-i": dynamic-complete-history
"\C-x\C-e": edit-and-execute-command
# emacs-editing-mode (not bound)
"\C-x)": end-kbd-macro
"\e>": end-of-history
"\C-e": end-of-line
"\eOF": end-of-line
"\e[F": end-of-line
"\C-x\C-x": exchange-point-and-mark
# forward-backward-delete-char (not bound)
# forward-byte (not bound)
"\C-f": forward-char
"\eOC": forward-char
"\e[C": forward-char
"\C-s": forward-search-history
"\ef": forward-word
"\eg": glob-complete-word
"\C-x*": glob-expand-word
"\C-xg": glob-list-expansions
# history-and-alias-expand-line (not bound)
"\e^": history-expand-line
# history-search-backward (not bound)
# history-search-forward (not bound)
# history-substring-search-backward (not bound)
# history-substring-search-forward (not bound)
"\e#": insert-comment
"\e*": insert-completions
"\e.": insert-last-argument
"\e_": insert-last-argument
"\C-k": kill-line
# kill-region (not bound)
# kill-whole-line (not bound)
"\ed": kill-word
# magic-space (not bound)
# menu-complete (not bound)
# menu-complete-backward (not bound)
"\C-n": next-history
"\eOB": next-history
"\e[B": next-history
"\en": non-incremental-forward-search-history
# non-incremental-forward-search-history-again (not bound)
"\ep": non-incremental-reverse-search-history
# non-incremental-reverse-search-history-again (not bound)
# old-menu-complete (not bound)
"\C-o": operate-and-get-next
# overwrite-mode (not bound)
"\C-x!": possible-command-completions
"\e=": possible-completions
"\e?": possible-completions
"\C-x/": possible-filename-completions
"\C-x@": possible-hostname-completions
"\C-x~": possible-username-completions
"\C-x$": possible-variable-completions
"\C-p": previous-history
"\eOA": previous-history
"\e[A": previous-history
# print-last-kbd-macro (not bound)
"\C-q": quoted-insert
"\C-v": quoted-insert
"\C-x\C-r": re-read-init-file
# redraw-current-line (not bound)
"\C-r": reverse-search-history
"\e\C-r": revert-line
"\er": revert-line
" ": self-insert
"!": self-insert
"\"": self-insert
"#": self-insert
"$": self-insert
"%": self-insert
"&": self-insert
"'": self-insert
"(": self-insert
")": self-insert
"*": self-insert
"+": self-insert
",": self-insert
"-": self-insert
".": self-insert
"/": self-insert
"0": self-insert
"1": self-insert
"2": self-insert
"3": self-insert
"4": self-insert
"5": self-insert
"6": self-insert
"7": self-insert
"8": self-insert
"9": self-insert
":": self-insert
";": self-insert
"<": self-insert
"=": self-insert
">": self-insert
"?": self-insert
"@": self-insert
"A": self-insert
"B": self-insert
"C": self-insert
"D": self-insert
"E": self-insert
"F": self-insert
"G": self-insert
"H": self-insert
"I": self-insert
"J": self-insert
"K": self-insert
"L": self-insert
"M": self-insert
"N": self-insert
"O": self-insert
"P": self-insert
"Q": self-insert
"R": self-insert
"S": self-insert
"T": self-insert
"U": self-insert
"V": self-insert
"W": self-insert
"X": self-insert
"Y": self-insert
"Z": self-insert
"[": self-insert
"\\": self-insert
"]": self-insert
"^": self-insert
"_": self-insert
"`": self-insert
"a": self-insert
"b": self-insert
"c": self-insert
"d": self-insert
"e": self-insert
"f": self-insert
"g": self-insert
"h": self-insert
"i": self-insert
"j": self-insert
"k": self-insert
"l": self-insert
"m": self-insert
"n": self-insert
"o": self-insert
"p": self-insert
"q": self-insert
"r": self-insert
"s": self-insert
"t": self-insert
"u": self-insert
"v": self-insert
"w": self-insert
"x": self-insert
"y": self-insert
"z": self-insert
"{": self-insert
"|": self-insert
"}": self-insert
"~": self-insert
"\200": self-insert
"\201": self-insert
"\202": self-insert
"\203": self-insert
"\204": self-insert
"\205": self-insert
"\206": self-insert
"\207": self-insert
"\210": self-insert
"\211": self-insert
"\212": self-insert
"\213": self-insert
"\214": self-insert
"\215": self-insert
"\216": self-insert
"\217": self-insert
"\220": self-insert
"\221": self-insert
"\222": self-insert
"\223": self-insert
"\224": self-insert
"\225": self-insert
"\226": self-insert
"\227": self-insert
"\230": self-insert
"\231": self-insert
"\232": self-insert
"\233": self-insert
"\234": self-insert
"\235": self-insert
"\236": self-insert
"\237": self-insert
"?": self-insert
"?": self-insert
"?": self-insert
"?": self-insert
"?": self-insert
"?": self-insert
"?": self-insert
"?": self-insert
"?": self-insert
"?": self-insert
"?": self-insert
"?": self-insert
"?": self-insert
"?": self-insert
"?": self-insert
"?": self-insert
"?": self-insert
"?": self-insert
"?": self-insert
"?": self-insert
"?": self-insert
"?": self-insert
"?": self-insert
"?": self-insert
"?": self-insert
"?": self-insert
"?": self-insert
"?": self-insert
"?": self-insert
"?": self-insert
"?": self-insert
"?": self-insert
"?": self-insert
"?": self-insert
"?": self-insert
"?": self-insert
"?": self-insert
"?": self-insert
"?": self-insert
"?": self-insert
"?": self-insert
"?": self-insert
"?": self-insert
"?": self-insert
"?": self-insert
"?": self-insert
"?": self-insert
"?": self-insert
"?": self-insert
"?": self-insert
"?": self-insert
"?": self-insert
"?": self-insert
"?": self-insert
"?": self-insert
"?": self-insert
"?": self-insert
"?": self-insert
"?": self-insert
"?": self-insert
"?": self-insert
"?": self-insert
"?": self-insert
"?": self-insert
"?": self-insert
"?": self-insert
"?": self-insert
"?": self-insert
"?": self-insert
"?": self-insert
"?": self-insert
"?": self-insert
"?": self-insert
"?": self-insert
"?": self-insert
"?": self-insert
"?": self-insert
"?": self-insert
"?": self-insert
"?": self-insert
"?": self-insert
"?": self-insert
"?": self-insert
"?": self-insert
"?": self-insert
"?": self-insert
"?": self-insert
"?": self-insert
"?": self-insert
"?": self-insert
"?": self-insert
"?": self-insert
"?": self-insert
"?": self-insert
"?": self-insert
"?": self-insert
"\C-@": set-mark
"\e ": set-mark
# shell-backward-kill-word (not bound)
# shell-backward-word (not bound)
"\e\C-e": shell-expand-line
# shell-forward-word (not bound)
# shell-kill-word (not bound)
# skip-csi-sequence (not bound)
"\C-x(": start-kbd-macro
# tab-insert (not bound)
"\e&": tilde-expand
"\C-t": transpose-chars
"\et": transpose-words
# tty-status (not bound)
"\C-x\C-u": undo
"\C-_": undo
# universal-argument (not bound)
# unix-filename-rubout (not bound)
"\C-u": unix-line-discard
"\C-w": unix-word-rubout
"\eu": upcase-word
# vi-append-eol (not bound)
# vi-append-mode (not bound)
# vi-arg-digit (not bound)
# vi-bWord (not bound)
# vi-back-to-indent (not bound)
# vi-backward-bigword (not bound)
# vi-backward-word (not bound)
# vi-bword (not bound)
# vi-change-case (not bound)
# vi-change-char (not bound)
# vi-change-to (not bound)
# vi-char-search (not bound)
# vi-column (not bound)
# vi-complete (not bound)
# vi-delete (not bound)
# vi-delete-to (not bound)
# vi-eWord (not bound)
# vi-editing-mode (not bound)
# vi-end-bigword (not bound)
# vi-end-word (not bound)
# vi-eof-maybe (not bound)
# vi-eword (not bound)
# vi-fWord (not bound)
# vi-fetch-history (not bound)
# vi-first-print (not bound)
# vi-forward-bigword (not bound)
# vi-forward-word (not bound)
# vi-fword (not bound)
# vi-goto-mark (not bound)
# vi-insert-beg (not bound)
# vi-insertion-mode (not bound)
# vi-match (not bound)
# vi-movement-mode (not bound)
# vi-next-word (not bound)
# vi-overstrike (not bound)
# vi-overstrike-delete (not bound)
# vi-prev-word (not bound)
# vi-put (not bound)
# vi-redo (not bound)
# vi-replace (not bound)
# vi-rubout (not bound)
# vi-search (not bound)
# vi-search-again (not bound)
# vi-set-mark (not bound)
# vi-subst (not bound)
# vi-tilde-expand (not bound)
# vi-unix-word-rubout (not bound)
# vi-yank-arg (not bound)
# vi-yank-pop (not bound)
# vi-yank-to (not bound)
"\C-y": yank
"\e.": yank-last-arg
"\e_": yank-last-arg
"\e\C-y": yank-nth-arg
"\ey": yank-pop

builtin

builtin [shell-builtin [args]]

シェルの組み込みコマンドを実行する。同一名の定義がある場合、オリジナルを呼び出したい場合に有効。shell-builtinが組み込みコマンドでない場合は、non-zeroを返す。

# sample.sh
# ++++++++++++++++++++++ [TEST-1 Commands] ++++++++++++++++++++++
_#() { echo "# $@"; }
function cd() {
  echo "my cd called: $@"
  builtin cd $@
}

_# "定義したcdを呼ぶ"
_# "そもそも同じ名前のコマンドを定義すべきでないというのはある"
_# "ここではサンプルとして実行している"
cd $PWD
pwd


# ******** [TEST-1 Output] ********
$ bash sample.sh 
# 定義したcdを呼ぶ
# そもそも同じ名前のコマンドを定義すべきでないというのはある
# ここではサンプルとして実行している
my cd called: /home/guest/workspace/bash
/home/guest/workspace/bash

# 終了ステータス
$ echo $?
0

caller

caller [expr]

アクティブなサブルーチン呼び出しのコンテキストを返す。スタックトレースの出力ができる。現在のフレームは0から始まる。exprには、正数を指定する。

  1. # sample.sh
  2. # ++++++++++++++++++++++ [TEST-1 Commands] ++++++++++++++++++++++
  3. _#() { echo "# $@"; }
  4. function func1() {
  5.   echo "==> func1"
  6.  
  7.   # スタックトレース
  8.   caller
  9.   caller 0
  10.   caller 1
  11.  
  12.   # func2の呼び出し
  13.   func2
  14. }
  15.  
  16.  
  17. function func2() {
  18.   echo "==> func2"
  19.  
  20.   # スタックトレース
  21.   caller
  22.   caller 0
  23.   caller 1
  24.   caller 2
  25. }
  26.  
  27. _# "func1呼び出し"
  28. func1
  29.  
  30. _# "mainのスタックトレース"
  31.  
  32. _# "main: caller"
  33. caller
  34. _# "main: caller 0"
  35. caller
  36. _# "main: caller 1"
  37. caller 1
  38.  
  39.  
  40. # ******** [TEST-1 Output] ********
  41. $ bash sample.sh
  42. # func1呼び出し
  43. ==> func1
  44. 28 sample.sh
  45. 28 main sample.sh
  46. ==> func2
  47. 13 sample.sh
  48. 13 func1 sample.sh
  49. 28 main sample.sh
  50. # mainのスタックトレース
  51. # main: caller
  52. 0 NULL
  53. # main: caller 0
  54. 0 NULL
  55. # main: caller 1
  56.  
  57. # 終了ステータス
  58. $ echo $?
  59. 1

command

command [-pVv] command [arguments …]

組み込みコマンドまたはPATH探索で見つけられるコマンドを実行する。-pは、パスのデフォルト定義を使う。コマンドが見つかれないまたはエラー終了した場合、終了コード127となる。-Vまたは-vは、コマンドの定義が表示される。

# sample.sh
# ++++++++++++++++++++++ [TEST-1 Commands] ++++++++++++++++++++++
_#() { echo "# $@"; }
mkdir -p /tmp/test/bin
cat <<COM > /tmp/test/bin/pwd
#!/bin/bash
echo "Current Path: \$(pwd)"
COM

chmod +x /tmp/test/bin/pwd
export PATH=/tmp/test/bin:$PATH

function pwd() {
  echo "my func pwd: $@"
  command pwd
}

_# "pwd実行"
pwd

_# "定義を出力"
_# "with -v"
command -v pwd

_# "with -V"
command -V pwd

_# "存在しないコマンド実行"
command unknown
echo $?

_# "PATHの/tmp/test/bin/pwdより組み込みpwdが優先される"
_# "command pwd"
command pwd
_# "command /tmp/test/bin/pwd"
command /tmp/test/bin/pwd
_# "command -p pwd"
command -p pwd
_# "command -p /tmp/test/bin/pwd"
command -p /tmp/test/bin/pwd


rm -rf /tmp/test/bin


# ******** [TEST-1 Output] ********
$ bash sample.sh 
# pwd実行
my func pwd: 
/home/guest/workspace/bash
# 定義を出力
# with -v
pwd
# with -V
pwd は関数です
pwd () 
{ 
    echo "my func pwd: $@";
    command pwd
}
# 存在しないコマンド実行
sample.sh: 行 29: unknown: コマンドが見つかりません
127
# PATHの/tmp/test/bin/pwdより組み込みpwdが優先される
# command pwd
/home/guest/workspace/bash
# command /tmp/test/bin/pwd
Current Path: /home/guest/workspace/bash
# command -p pwd
/home/guest/workspace/bash
# command -p /tmp/test/bin/pwd
Current Path: /home/guest/workspace/bash

# 終了ステータス
$ echo $?
0

declare

declare [-aAfFgilnrtux] [-p] [name[=value] …]

変数の宣言と属性の設定を行なう。nameが指定されない場合、変数の値が表示される。

  • -pオプションは、nameの属性と値を表示する。
  • -fオプションは、関数定義を表示する。-Fは、関数名と属性のみを表示する。
  • -gオプションは、グローバルスコープにする。

以下のオプションがある。

  • -a
    • 配列
  • -A
    • 連想配列
  • -f
    • 関数名のみ
  • -i
    • 整数値
  • -n
    • 名前参照
  • -r
    • 読み取り専用
  • -t
    • トレース属性、DEBUGRETURNトラップを継承
  • -u
    • 小文字は大文字に変換される
  • -x
    • 環境変数としてexportされる

+は、属性を無効にする。localコマンドと同様、関数内でdeclareを使用すると関数ローカルになる。

echo

echo [-neE] [arg …]

スペース区切りでargsを出力する。改行で終了する。-nオプションは、改行を抑制する。-eオプションは、エスケープが有効となる。-Eオプションは、エスケープ文字を無効化する。オプションの終了を意味する--を解釈しない。

# sample.sh
# ++++++++++++++++++++++ [TEST-1 Commands] ++++++++++++++++++++++
_#() { echo "# $@"; }
_# "エスケープ"
echo -e "a	b"

_# "改行なし"
echo -n "foo"

_# "エスケープなし"
echo -E "b\ar"

_# "オプション区切り -- 無効"
echo -- bar


# ******** [TEST-1 Output] ********
$ bash sample.sh 
# エスケープ
a	b
# 改行なし
foo# エスケープなし
b\ar
# オプション区切り -- 無効
-- bar

# 終了ステータス
$ echo $?
0

enable

enable [-a] [-dnps] [-f filename] [name …]

組み込みコマンドの有効および無効化する。-nオプションは、namesを無効にする。

  • pオプションは、シェルの組み込みコマンドのリストが表示される。引数なしの場合、全ての有効なシェル組み込みコマンドからなるリストが表示される。-aオプションは、有効または無効を示すコマンドリストを表示する。
  • fオプションは、shared objectファイルから新しい組み込みコマンドをロードする。-dオプションは、-fでロードされたコマンドを削除する。

引数なしの場合、シェルの組み込みコマンドリストが表示される。-sオプションは、POSIX特有の組み込みに制限する。

# sample.sh
# ++++++++++++++++++++++ [TEST-1 Commands] ++++++++++++++++++++++
_#() { echo "# $@"; }
_# "一覧表示"
enable

_# "一覧表示(有効/無効のindication付き)"
_# "testを無効化"
enable -n test
enable -p -a test

cat <<STR > test
#!/bin/bash
echo "my test"
exit 3
STR

chmod +x test
export PATH=.:$PATH

_# "test 1 -eq 1"
test 1
echo $?


# ******** [TEST-1 Output] ********
$ bash sample.sh 
# 一覧表示
enable .
enable :
enable [
enable alias
enable bg
enable bind
enable break
enable builtin
enable caller
enable cd
enable command
enable compgen
enable complete
enable compopt
enable continue
enable declare
enable dirs
enable disown
enable echo
enable enable
enable eval
enable exec
enable exit
enable export
enable false
enable fc
enable fg
enable getopts
enable hash
enable help
enable history
enable jobs
enable kill
enable let
enable local
enable logout
enable mapfile
enable popd
enable printf
enable pushd
enable pwd
enable read
enable readarray
enable readonly
enable return
enable set
enable shift
enable shopt
enable source
enable suspend
enable test
enable times
enable trap
enable true
enable type
enable typeset
enable ulimit
enable umask
enable unalias
enable unset
enable wait
# 一覧表示(有効/無効のindication付き)
# testを無効化
enable .
enable :
enable [
enable alias
enable bg
enable bind
enable break
enable builtin
enable caller
enable cd
enable command
enable compgen
enable complete
enable compopt
enable continue
enable declare
enable dirs
enable disown
enable echo
enable enable
enable eval
enable exec
enable exit
enable export
enable false
enable fc
enable fg
enable getopts
enable hash
enable help
enable history
enable jobs
enable kill
enable let
enable local
enable logout
enable mapfile
enable popd
enable printf
enable pushd
enable pwd
enable read
enable readarray
enable readonly
enable return
enable set
enable shift
enable shopt
enable source
enable suspend
enable -n test
enable times
enable trap
enable true
enable type
enable typeset
enable ulimit
enable umask
enable unalias
enable unset
enable wait
# test 1 -eq 1
my test
3

# 終了ステータス
$ echo $?
0

help

help [-dms] [pattern]

組み込みコマンドのヘルプを表示する。

  • -dオプション
    • 概要説明
  • -m
    • manページ形式
  • -s
    • short usage
$ help command
command: command [-pVv] command [arg ...]
    Execute a simple command or display information about commands.

    Runs COMMAND with ARGS suppressing  shell function lookup, or display
    information about the specified COMMANDs.  Can be used to invoke commands
    on disk when a function with the same name exists.

    Options:
      -p    use a default value for PATH that is guaranteed to find all of
            the standard utilities
      -v    print a description of COMMAND similar to the `type' builtin
      -V    print a more verbose description of each COMMAND

    Exit Status:
    Returns exit status of COMMAND, or failure if COMMAND is not found.

$ help -d command
command - Execute a simple command or display information about commands.

$ help -m command
NAME
    command - Execute a simple command or display information about commands.

SYNOPSIS
    command [-pVv] command [arg ...]

DESCRIPTION
    Execute a simple command or display information about commands.

    Runs COMMAND with ARGS suppressing  shell function lookup, or display
    information about the specified COMMANDs.  Can be used to invoke commands
    on disk when a function with the same name exists.

    Options:
      -p    use a default value for PATH that is guaranteed to find all of
            the standard utilities
      -v    print a description of COMMAND similar to the `type' builtin
      -V    print a more verbose description of each COMMAND

    Exit Status:
    Returns exit status of COMMAND, or failure if COMMAND is not found.

SEE ALSO
    bash(1)

IMPLEMENTATION
    GNU bash, バージョン 4.4.23(1)-release (x86_64-apple-darwin17.5.0)
    Copyright (C) 2016 Free Software Foundation, Inc.
    ライセンス GPLv3+: GNU GPL バージョン 3 またはそれ以降 <http://gnu.org/licenses/gpl.html>

$ help -s command
command: command [-pVv] command [arg ...]

let

let expression [expression …]

算術計算を行なう。expressionが0に評価される場合、letは1を返す。そうでない場合は0。

# sample.sh
# ++++++++++++++++++++++ [TEST-1 Commands] ++++++++++++++++++++++
_#() { echo "# $@"; }
declare -i i=10

_# "i+=1"
let i+=1
echo $i

_# "( )"
let i=( i * 10 )
echo $i

_# "i++"
let i++
echo $i

_# "i=0"
_# "終了コードに注意"
let i=0
echo $?

_# "i=1"
_# "終了コードに注意"
let ++i
echo $?


# ******** [TEST-1 Output] ********
$ bash sample.sh 
# i+=1
11
# ( )
110
# i++
111
# i=0
# 終了コードに注意
1
# i=1
# 終了コードに注意
0

# 終了ステータス
$ echo $?
0

local

local [option] name[=value] …

ローカルな変数として定義する。localは関数内でのみ利用可能。localは、関数とその子に可視スコープを制限する。

# sample.sh
# ++++++++++++++++++++++ [TEST-1 Commands] ++++++++++++++++++++++
_#() { echo "# $@"; }
global=global

function test_local() {
  echo "=> test_local"

  # globalは、test_localと子で可視
  local global
  echo $global

  global="test_local"
  echo $global

  # test_local2でglobalは上書きされる
  test_local2
  echo $global

  echo "=> end test_local"
}

function test_local2() {
  echo "==> test_local2"

  # test_local2ではglobalはlocal定義していない
  echo $global

  # test_localのglobalを上書き
  global="test_local2"
  echo $global

  echo "==> end test_local2"
}

_# "globalを定義する関数を呼び出し"
test_local

_# "echo global"
echo $global


# ******** [TEST-1 Output] ********
$ bash sample.sh 
# globalを定義する関数を呼び出し
=> test_local

test_local
==> test_local2
test_local
test_local2
==> end test_local2
test_local2
=> end test_local
# echo global
global

# 終了ステータス
$ echo $?
0

logout

logout [n]

ログインシェルを終了する。親シェルにステータスnを返す。

$ logout
Saving session...
...copying shared history...
...saving history...truncating history files...
...completed.

[プロセスが完了しました]

mapfile

mapfile [-d delim] [-n count] [-O origin] [-s count]
    [-t] [-u fd] [-C callback] [-c quantum] [array]

標準入力またはディスクリプタfdから行を読みインデックス配列へ格納する。デフォルトの配列変数はMAPFILE

  • -d
    • 区切り文字、デフォルトは改行
  • -n
    • 最大行数
  • -O
    • インデックスorigin
  • -s
    • 最初のcount行を無視
  • -t
    • 読み取られた行からdelimを除去(デフォルトは改行)
  • -u
    • ディスクリプタfdから読む
  • -C
    • quantumが読まれるごとに呼ばれるコールバック。-cが指定されない場合は、デフォルトは5000。
  • -c
    • コールバックを呼ぶ行数
$ mapfile
1
2
3
$ echo ${MAPFILE[0]}
1
$ echo ${MAPFILE[@]}
1 2 3

# インデックス1に格納
$ mapfile -O 1
4
$ echo ${MAPFILE[@]}
1 4 3

# 改行を続けて入力
# 何もない行はNULで配列に格納されている
$ mapfile 
1


4
$ echo ${MAPFILE[0]}
1
$ echo ${MAPFILE[3]}
4

# コールバック付き
# 2行ごとにコールバックを実行
# 引数には、次に割り当てられるインデックスと要素が渡される
# コールバック実行時、要素は未割り当てである
$ mapfile -C 'IFS=" "; printf "# ARRAY=%s\n# " "${MAPFILE[*]}"; echo' -c 2 -t
a
b
# ARRAY=a
# 1 b
c
d
# ARRAY=a b c
# 3 d

printf

printf [-v var] format [arguments]

フォーマット形式に沿って出力する。-vオプションは、出力結果をvarに格納する。

# sample.sh
# ++++++++++++++++++++++ [TEST-1 Commands] ++++++++++++++++++++++
_#() { printf "%s\n" "#$@"; }
_# "1 フォーマット指定なし"
printf "Hello, World\n"
echo "exit: $?"

_# "2 エスケープ文字は解釈されない"
printf "%s\n" "Hello,\nWorld\n"
echo "exit: $?"

_# "3 エスケープ文字も解釈される"
printf "%b" "Hello,\nWorld\n"
echo "exit: $?"

_# "4 フォーマット指定より多い引数を与える"
printf "%b" "Hello,\nWorld\n" "Hello,\nWorld\n"
echo "exit: $?"

_# "5 出力を変数に代入"
printf -v var "%b" "Hello, World\n"
printf "$var"

_# "6 シェルインプット可能形式で出力する"
printf "%q\n" "Hello World\n"

_# "7 日付関連の出力"
_# "  strftime"
_# "  参考: https://linuxjm.osdn.jp/html/LDP_man-pages/man3/strftime.3.html"
printf "%(%Y-%m-%d %H:%M:%S)T\n"
printf "%(%F %T)T\n"
printf "%(%F %T%z)T\n"

function log() {
  printf "%(%F %T)T: %b\n" -1 "$*"
}
log "exit successfully"


# ******** [TEST-1 Output] ********
$ bash sample.sh 
#1 フォーマット指定なし
Hello, World
exit: 0
#2 エスケープ文字は解釈されない
Hello,\nWorld\n
exit: 0
#3 エスケープ文字も解釈される
Hello,
World
exit: 0
#4 フォーマット指定より多い引数を与える
Hello,
World
Hello,
World
exit: 0
#5 出力を変数に代入
Hello, World
#6 シェルインプット可能形式で出力する
Hello\ World\\n
#7 日付関連の出力
#  strftime
#  参考: https://linuxjm.osdn.jp/html/LDP_man-pages/man3/strftime.3.html
2019-02-02 10:17:12
2019-02-02 10:17:12
2019-02-02 10:17:12+0900
2019-02-02 10:17:12: exit successfully

# 終了ステータス
$ echo $?
0

read

read [-ers] [-a aname] [-d delim] [-i text] [-n nchars]
    [-N nchars] [-p prompt] [-t timeout] [-u fd] [name …]

標準入力またはfdから行を読み取り単語に分割してname..に代入する。単語は、IFSで分割される。単語分割後の値がnameより少ない場合は、残り全ての単語が最後のnameに代入される。少ない場合は、空の値となる。

nameが指定されない場合は、REPLYに値が代入される。

# read-sample1.sh
# ++++++++++++++++++++++ [TEST-1 Commands] ++++++++++++++++++++++
_#() { printf "%s\n" "#$@"; }
_# " 標準入力から行を読み取り、word1とword2へ代入"
read word1 word2
echo $word1
echo $word2


# ******** [TEST-1 Output] ********
$ bash  read-sample1.sh
# 標準入力から行を読み取り、word1とword2へ代入
hello world
hello
world
# read-sample2.sh
# ++++++++++++++++++++++ [TEST-2 Commands] ++++++++++++++++++++++
_#() { printf "%s\n" "#$@"; }
_# " REPLYから読み取り"
read
echo $REPLY


# ******** [TEST-2 Output] ********
$ bash  read-sample2.sh
# REPLYから読み取り
hello world
hello world
# read-sample3.sh
# ++++++++++++++++++++++ [TEST-3 Commands] ++++++++++++++++++++++
_#() { printf "%s\n" "#$@"; }
_# " 配列に代入"
declare -a array=()
read -a array
echo ${array[0]}


# ******** [TEST-3 Output] ********
$ bash  read-sample3.sh
# 配列に代入
hello world
hello
# read-sample4.sh
# ++++++++++++++++++++++ [TEST-4 Commands] ++++++++++++++++++++++
_#() { printf "%s\n" "#$@"; }
_# " 行区切りを.にする"
read -d .
echo $REPLY


# ******** [TEST-4 Output] ********
$ bash  read-sample4.sh
# 行区切りを.にする
hello world
hello.hello world hello
# read-sample5.sh
# ++++++++++++++++++++++ [TEST-5 Commands] ++++++++++++++++++++++
_#() { printf "%s\n" "#$@"; }
_# " 指定文字数だけ読む"
read -n 10 chars
echo "chars: $chars"


# ******** [TEST-5 Output] ********
$ bash  read-sample5.sh
# 指定文字数だけ読む
1234567890chars: 1234567890
# read-sample6.sh
# ++++++++++++++++++++++ [TEST-6 Commands] ++++++++++++++++++++++
_#() { printf "%s\n" "#$@"; }
_# " プロンプトに表示する"
read -p "input> " line
echo "input: $line"


# ******** [TEST-6 Output] ********
$ bash read-sample6.sh
# プロンプトに表示する
input> inputline
input: inputline
# read-sample7.sh
# ++++++++++++++++++++++ [TEST-7 Commands] ++++++++++++++++++++++
_#() { printf "%s\n" "#$@"; }
_# " タイムアウト付きで読み取る(10sec)"
read -t 10
echo $REPLY


# ******** [TEST-7 Output] ********
$ bash  read-sample7.sh
# タイムアウト付きで読み取る(10sec), aだけ打って待つ
a
# read-sample8.sh
# ++++++++++++++++++++++ [TEST-8 Commands] ++++++++++++++++++++++
_#() { printf "%s\n" "#$@"; }
_# " ファイルから読む"
cat dummy.txt

while IFS= read line; do
 echo $line
done < dummy.txt


# ******** [TEST-8 Output] ********
$ bash  read-sample8.sh
# ファイルから読む
hello
world
hello
world

readarray

readarray [-d delim] [-n count] [-O origin] [-s count]
    [-t] [-u fd] [-C callback] [-c quantum] [array]

標準入力またはディスクリプタfdから行を読み取り配列へ格納する。mapfileの別名。

source

source filename

. の別名。

type

type [-afptP] [name …]

nameの解釈を表示する。

  • -tオプションは、alias、function、builtin、file、keywordのいずれかを示す。
  • -pオプションは、実行されるディスク上のファイル名を表示する。-Pオプションは、パス内から探す。
  • -aオプションは、実行可能なコマンドを全て表示する。
  • -fオプションは、シェル関数は除外する。
# nameの種別を表示する
$ type -t la
alias
$ type -t command
builtin
$ type -t while
keyword
$ function my() { echo "my"; }
$ type my
my は関数です
my () 
{ 
    echo "my"
}
$ type -t my
function

# 実行可能なコマンド全て表示
$ type -a ls
ls`ls -G` のエイリアスです
ls/bin/ls です

# PATH内のlsを探す
$ type -P ls
/bin/ls
$ which ls
/bin/ls

# 実行されるディスク上のファイル名を表示する
$ type -p cut
/usr/bin/cut
$ type -p ls  # ビルドインは何も表示されない

typeset

typeset [-afFgrxilnrtux] [-p] [name[=value] …]

declareの別名。

ulimit

ulimit [-HSabcdefiklmnpqrstuvxPT] [limit]

シェルで起動されるプロセスが利用可能なリソース制御をする。

オプション説明
-Sソフトリミット
-Hハードリミット
-a現在のリミットを表示
-bソケットバッファの最大サイズ
-cコアファイルの最大数
-dプロセスのデータセグメントの最大サイズ
-eプロセススケジュール優先度の最大値
-fファイルの最大サイズ
-iペンディングされるシグナルの最大数
-kkqueuesの最大数
-lロックされるメモリの最大サイズ
-m使用される最大メモリサイズ
-nオープンできるファイルディスクリプタの最大数
-pパイプバッファサイズ
-qPOSIXメッセージキューの最大バイト数
-rリアルタイムスケジューリング優先度の最大値
-s最大スタックサイズ
-t秒単位でのCPU時間の最大量
-uシングルユーザで利用可能な最大プロセス数
-xファイルロックの最大数
-P擬似端末の最大数
-Tスレッドの最大数

例:現在の上限を表示する

[vagrant@localhost ~]$ ulimit -a
core file size          (blocks, -c) 0
data seg size           (kbytes, -d) unlimited
scheduling priority             (-e) 0
file size               (blocks, -f) unlimited
pending signals                 (-i) 1880
max locked memory       (kbytes, -l) 64
max memory size         (kbytes, -m) unlimited
open files                      (-n) 1024
pipe size            (512 bytes, -p) 8
POSIX message queues     (bytes, -q) 819200
real-time priority              (-r) 0
stack size              (kbytes, -s) 8192
cpu time               (seconds, -t) unlimited
max user processes              (-u) 1880
virtual memory          (kbytes, -v) unlimited
file locks                      (-x) unlimited

例:オープンできるファイルディスクリプタの最大数を変更する

[vagrant@localhost ~]$ ulimit -Hn
2048
[vagrant@localhost ~]$ ulimit -n $((1024*2))
[vagrant@localhost ~]$ ulimit -n
2048

ulimitの設定の反映は、systemd制御下のデーモン管理との兼ね合いもある。詳しくはsystemd、limitsなどのキーワードで調べる必要あり。

$ cat <<EOF > Vagrantfile 
# -*- mode: ruby -*-
# vi: set ft=ruby :

# All Vagrant configuration is done below. The "2" in Vagrant.configure
# configures the configuration version (we support older styles for
# backwards compatibility). Please don't change it unless you know what
# you're doing.
Vagrant.configure("2") do |config|
  config.vm.box = "centos/7"
  config.vm.provision "shell", inline: <<-SHELL
    yum update -y
  SHELL
end
EOF
$ vagrant up
$ vagrant ssh

[vagrant@localhost ~]$ cat /etc/systemd/user.conf 
#  This file is part of systemd.
#
#  systemd is free software; you can redistribute it and/or modify it
#  under the terms of the GNU Lesser General Public License as published by
#  the Free Software Foundation; either version 2.1 of the License, or
#  (at your option) any later version.
#
# You can override the directives in this file by creating files in
# /etc/systemd/user.conf.d/*.conf.
#
# See systemd-user.conf(5) for details

[Manager]
#LogLevel=info
#LogTarget=console
#LogColor=yes
#LogLocation=no
#SystemCallArchitectures=
#TimerSlackNSec=
#DefaultTimerAccuracySec=1min
#DefaultStandardOutput=inherit
#DefaultStandardError=inherit
#DefaultTimeoutStartSec=90s
#DefaultTimeoutStopSec=90s
#DefaultRestartSec=100ms
#DefaultStartLimitInterval=10s
#DefaultStartLimitBurst=5
#DefaultEnvironment=
#DefaultLimitCPU=
#DefaultLimitFSIZE=
#DefaultLimitDATA=
#DefaultLimitSTACK=
#DefaultLimitCORE=
#DefaultLimitRSS=
#DefaultLimitNOFILE=
#DefaultLimitAS=
#DefaultLimitNPROC=
#DefaultLimitMEMLOCK=
#DefaultLimitLOCKS=
#DefaultLimitSIGPENDING=
#DefaultLimitMSGQUEUE=
#DefaultLimitNICE=
#DefaultLimitRTPRIO=
#DefaultLimitRTTIME=

参考 http://man7.org/linux/man-pages/man5/systemd-system.conf.5.html

unalias

unalias [-a] [name … ]

aliasを削除する。-aオプションで、全てのaliasを削除する。

$ alias hoge
alias hoge='ls -la'
$ unalias hoge
$ alias hoge
-bash: alias: hoge: 見つかりません

シェルの振る舞い修正

set

set [--abefhkmnptuvxBCEHPT] [-o option-name] [argument …]
set [+abefhkmnptuvxBCEHPT] [+o option-name] [argument …]

shopt

shopt [-pqsu] [-o] [optname …]

シェル変数

URL https://www.gnu.org/software/bash/manual/bash.html#Bourne-Shell-Variables

ジョブ制御

表記説明
%nジョブ番号n
%SSで始まるジョブ
%?S文字列Sを含むジョブ
%%現在のジョブ
%+現在のジョブ
%-前のジョブ

例:pingで始まるジョブを表示

$ ping localhost > /dev/null &
[1] 37862
$ jobs %ping
[1]+  実行中               ping localhost > /dev/null &

ジョブ制御コマンド

bg

bg [jobspec …]

中断されたジョブをバックグラウンドで再開する。jobspecが指定されない場合は現在のジョブが使われる。

$ sleep 140 
^Z
[1]+  停止                  sleep 140
$ bg
[1]+ sleep 140 &
$ jobs
[1]+  実行中               sleep 140 &

fg

fg [jobspec]

フォアグラウンドでジョブを再開する。jobspecが指定されない場合は現在のジョブが使われる。

$ sleep 140 &
[1] 42581
$ fg
sleep 140

jobs

jobs [-lnprs] [jobspec]
jobs -x command [arguments]

アクティブなジョブ一覧を表示する。

オプション説明
-lプロセスIDも表示する
-n最後に状態の変わったジョブのみを表示する
-pプロセスのグループリーダのプロセスIDのみ表示する
-r実行中のジョブのみ表示する
-s停止中のジョブのみ表示する
$ sleep 180 &
[1] 44141
$ sleep 280 &
[2] 44156
$ jobs
[1]-  実行中               sleep 180 &
[2]+  実行中               sleep 280 &
$ jobs -n
$ jobs -n
$ jobs -r
[1]-  実行中               sleep 180 &
[2]+  実行中               sleep 280 &
$ jobs -s
$ jobs -p
44141
44156
$ jobs -l
[1]- 44141 実行中               sleep 180 &
[2]+ 44156 実行中               sleep 280 &

kill

kill [-s sigspec] [-n signum] [-sigspec] jobspec or pid
kill -l|-L [exit_status]

ジョブまたはプロセスにシグナルを送る。シグナルの指定がない場合は、SIGTERMが送られる。-lオプションは、シグナル名一覧を表示する。-Lは-lと等しい。

例:バックグラウンドで実行中のプロセスにシグナルを送る

# ジョブ番号を指定してSIGTERMを送る
$ sleep 120 &
[1] 14120
$ kill %1
[1]+  Terminated: 15          sleep 120

# プロセスIDを指定してSIGKILLを送る
$ sleep 120 &
[1] 14607
$ kill -SIGKILL 14607
[1]+  Killed: 9               sleep 120

# シグナル番号15(SIGTERM)をジョブ番号1に送る
$ sleep 120 &
[1] 15222
$ kill -n 15 %1
[1]+  Terminated: 15          sleep 120

例:シグナル一覧を表示する

$ kill -L
 1) SIGHUP	 2) SIGINT	 3) SIGQUIT	 4) SIGILL	 5) SIGTRAP
 6) SIGABRT	 7) SIGEMT	 8) SIGFPE	 9) SIGKILL	10) SIGBUS
11) SIGSEGV	12) SIGSYS	13) SIGPIPE	14) SIGALRM	15) SIGTERM
16) SIGURG	17) SIGSTOP	18) SIGTSTP	19) SIGCONT	20) SIGCHLD
21) SIGTTIN	22) SIGTTOU	23) SIGIO	24) SIGXCPU	25) SIGXFSZ
26) SIGVTALRM	27) SIGPROF	28) SIGWINCH	29) SIGINFO	30) SIGUSR1
31) SIGUSR2	

$ kill -l
 1) SIGHUP	 2) SIGINT	 3) SIGQUIT	 4) SIGILL	 5) SIGTRAP
 6) SIGABRT	 7) SIGEMT	 8) SIGFPE	 9) SIGKILL	10) SIGBUS
11) SIGSEGV	12) SIGSYS	13) SIGPIPE	14) SIGALRM	15) SIGTERM
16) SIGURG	17) SIGSTOP	18) SIGTSTP	19) SIGCONT	20) SIGCHLD
21) SIGTTIN	22) SIGTTOU	23) SIGIO	24) SIGXCPU	25) SIGXFSZ
26) SIGVTALRM	27) SIGPROF	28) SIGWINCH	29) SIGINFO	30) SIGUSR1
31) SIGUSR2

例:シグナル番号のシグナルを表示する

$ kill -l 15
TERM
$ kill -L 9
KILL

wait

wait [-fn] [jobspec or pid …]

jobspecまたはpidの終了を待つ。jobspecまたはpidの指定がない場合、現在アクティブの全ての子プロセスを待つ。-nオプションは、いずれかのジョブが終了するのを待つ。

例;バックグラウンドのジョブの終了を待つ

# ジョブ1と2の終了を待つ
$ sleep 10 &
[1] 18848
$ sleep 20 &
[2] 18854
$ wait
[1]-  終了                  sleep 10
[2]+  終了                  sleep 20

例:バックグラウンドのいずれかのジョブが終了するのを待つ

$ sleep 10 &
[1] 19591
$ sleep 20 &
[2] 19612
$ wait -n
[1]-  終了                  sleep 10

disown

disown [-ar] [-h] [jobspec … | pid … ]

アクティブなジョブテーブルからジョブを削除する。-hオプションは、ジョブはテーブルから削除されずSIGHUPが送られないようにする。

例:バックグラウンドジョブをアクティブなジョブテーブルから削除する

# 実行中のジョブを停止する
$ sleep 130 &
[1] 24765
$ jobs
[1]+  実行中               sleep 130 &
$ disown %1
$ jobs
# 停止中のジョブを停止する
$ sleep 130 &
[1] 25215
$ jobs
[1]+  実行中               sleep 130 &
$ fg %1
sleep 130
^Z
[1]+  停止                  sleep 130
$ disown -r %1
-bash: 警告: プロセスグループ 25215 のジョブ 1 を削除しています
$ jobs

suspend

suspend [-f]

ジョブを中断する。SIGCONTで再開する。ログインシェルはサスペンドされない。Ctrl-Zによる中断に等しい。

例;rootで作業中にシェルを一時中断する

[vagrant@localhost ~]$ su 
パスワード:
[root@localhost vagrant]# ls
foo  test.sh
[root@localhost vagrant]# suspend

[1]+  停止                  su
[vagrant@localhost ~]$ fg
su
[root@localhost vagrant]# 
 

参考

コマンド履歴

fc

fc [-e ename] [-lnr] [first] [last]
fc -s [pat=rep] [command]

コマンド履歴を編集し再実行する。

$ history 3
  557  ls
  558  sleep 30 &
  559  history 3
# viで557-558番を編集
$ fc 557 558
ls
sleep 30 &

history

history [n]
history -c
history -d offset
history -d start-end
history [-anrw] [filename]
history -ps arg

コマンド履歴を表示する。

例:履歴の最後の3行を表示する

$ history 3
  560  ls
  561  sleep 30 &
  562  history 3

例:コマンド履歴完全クリアする

$ :> ~/.bash_history && history -c

履歴展開

!nn番目のコマンド行を展開
!-nn番前のコマンド行を展開
!!!-1に等しい
!stringstringで始まる最も最近のコマンドを展開
!?string[?]stringを含む最も最近のコマンドを展開
^string1^string2^最後のコマンドでstring1をstring2に置換して展開
!#現在入力中のコマンド全体
$ history
    1  cat ~/.bash_history 
    2  echo $A
    3  history 1
    4  history 3
    5  A=100 echo $A
    6  history 
    7  echo $A
    8  echo $A A=200
    9  A=300 echo $A
   10  ! 2
   11  !=2
   12  history
   13  echo $A A=200
   14  ls ls ls ls 
   15  history
$ !?hist
history
    1  cat ~/.bash_history 
    2  echo $A
    3  history 1
    4  history 3
    5  A=100 echo $A
    6  history 
    7  echo $A
    8  echo $A A=200
    9  A=300 echo $A
   10  ! 2
   11  !=2
   12  history
   13  echo $A A=200
   14  ls ls ls ls 
   15  history
   16  history
$ echo "foo"; !#
echo "foo"; echo "foo"; 
foo
foo

参考リンク


*1

PR

トップ   差分 バックアップ リロード   一覧 単語検索 最終更新   ヘルプ   最終更新のRSS
Last-modified: 2019-07-24 (水) 22:54:44 (81d)
目次
ダブルクリックで閉じるTOP | 閉じる
GO TO TOP