#author("2019-03-17T01:35:17+00:00","default:haikikyou","haikikyou")
Bashマニュアルを参照に、動作確認結果を追記し整理したメモ。
#author("2019-03-17T07:12:00+00:00","default:haikikyou","haikikyou")
Bashマニュアルを参照し、動作確認結果を追記し整理したメモ。


#contents

* コマンド [#j1138789]

** ループ [#da0f596d]

*** until [#r0605004]

 until test-commands; do consequent-commands; done

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

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

rm "job.done"

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

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

# --- output ---

success
}}}

*** while [#ec71bf2b]

 while test-commands; do consequent-commands; done

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

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

touch "job.lock"

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

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

# --- output ---

job done
}}}


*** for [#k816bb81]

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

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

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

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

# --- output ---

localhost
192.168.3.4


# --- commands ---

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

# --- output ---

0
1
2
3
4
}}}


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

*** if [#l6343e8e]

#geshi(bash){{{
if test-commands; then
  consequent-commands;
[elif more-test-commands; then
  more-consequents;]
[else alternate-consequents;]
fi
}}}

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

#geshi(bash){{{
# --- 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 [#l507c23a]
#geshi(bash){{{
case word in
    [ [(] pattern [| pattern]…) command-list ;;]…
esac
}}}

- switch case文のようなコマンド。
- ;; で条件区切りとなる。;&、;;& という指定もある。;& は、フォールスルーする。;;& は、次にマッチする条件があるまで辿り、マッチすればcommand-listを実行する。
- patternは、パターンマッチングのルールによって処理される。
- | で区切れば、ORでpatternを複数書くことができる。
- どのpatternにもマッチしない場合は、終了ステータス0となり、マッチすれば、command-listの終了ステータスとなる。

#geshi(bash){{{
# --- 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 [#x4e2affc]

- メニューを生成するコマンド。複数のリストから選択させたい場合に便利である。
- 

#geshi(bash){{{
select name [in words …]; do commands; done
}}}

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

#geshi(bash){{{
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がセットされる

#geshi(bash){{{
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)
}}}

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

#geshi(bash){{{
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に値を設定すると、選択時のメッセージをカスタマイズすることができる。

#geshi(bash){{{
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)) [#v3a6495d]
&aname(Shell_Arithmetic){};

 (( expression ))  
 let "expression"

expressionの結果がゼロでない場合、0が返る。そうでない場合は、1。&code(){true};、&code(){false}; を指定しても結果は非ゼロである。

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

#geshi(bash){{{
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 ]] [#j04884c5]

#geshi(bash){{{
[[ expression ]]
}}}

- [[ は組み込みコマンドである。[ と振る舞いが異なることに注意。
- [[ と ]]; の間のexpressionでは展開の扱いが異なる。
-- 単語分割、ファイル名展開は行われない。
-- チルダ展開、パラメータ・変数展開、算出式展開、コマンド置換、プロセス置換、クオート除去は作用する。
- >、< は、現在のlocaleで作用する。
- ==、!= は、右辺がパターンマーチングのルールで判定される。&code(){extglob}; オプションが有効な場合、= は、 == と等しい。&code(){nocasematch}; オプションが有効な場合、アルファベットの文字大小を無視する。終了ステータスは、== でマッチ、!= でマッチしないを満たす場合は0、そう出ない場合は1。
- =~ は、右辺の文字列をPOSIX拡張正規表現として動作する。正規表現構文が正しくない場合、終了ステータスは2となる。グルーピングにマッチした文字列は、&code(){BASH_REMATCH};配列に保存される。

#geshi(bash){{{
# --- 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 ) [#x75a8176]

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

#geshi(bash){{{
# --- 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 [#he71520c]

expressionがtrueならばfalseを返す。

#geshi(bash){{{
# --- 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 [#dc7f9333]

expression1で全体の真、偽が決まる場合は、expression2を評価しない。
- expression1 && expression2
-- expression1がfalse、expression2は評価しない、結果はfalse
- expression1 || expression2
-- expression1がtrue、expression2は評価しない、結果はtrue

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

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

** グルーピング [#jc6384ba]

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

#geshi(bash){{{
# --- 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; } [#a57f16fe]
現在のコンテキストで実行される。}の後にリダイレクトを指定すると、グルーピングされたコマンドの結果をリダイレクト先にまとめて送ることができる。

#geshi(bash){{{
# --- 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
}}}

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


** Coprocesses [#q3c85d6a]

#geshi(bash){{{
coproc [NAME] command [redirections]
}}}

- サブシェルで非同期にコマンド実行することができる。&を使ったバックグラウンド実行と似ている。coprocで実行されたシェルと実行元のシェル間にはstdinとstdoutのパイプが作られる。pipeを呼び、forkしている。
- コマンドのstdoutは NAME[0]、stdinは NAME[1]

#geshi(bash){{{
# --- 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 [#t34180a7]

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

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

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

https://linuxjm.osdn.jp/html/GNU_findutils/man1/xargs.1.html
* 関数 [#q23215f5]

#geshi(bash){{{
name () compound-command [ redirections ]
function name [()] compound-command [ redirections ]
}}}

- 関数の終了ステータスは、関数内の最後のコマンドの終了ステータスとなる。return [終了ステータス]で関数を抜ける。終了ステータスが指定されない場合は、returnの前にコマンドの終了ステータスとなる。
- 引数は、&code(){$1〜};の様な位置パラメータや&code(){$@};、&code(){$*};で参照可能である。関数を終了すると、これらの値は関数実行前の状態にリストアされる(関数内でshiftしたりしても、関数を抜けると呼び出し前の状態である)。
- &code(){FUNCNAME};は、実行中の関数名が入っている。FUNCNAMEは、配列でネストした関数スタックを取得できる。
- &code(){unset -f };で定義済み関数を削除できる。
- local指定で、関数ローカルな変数定義ができる。グローバルと同様な名前であっても、local指定していれば関数内ではlocalで指定した値を使用でき、グルーバルを汚染しない。変数の可視性は動的スコープ。
- declare -f、typeset -fで関数定義を確認できる。-Fオプションの場合は名前のみを表示する。

#geshi(bash){{{
# --- 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
}}}

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

#geshi(bash){{{
# --- 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となる。

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

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

f1() {
  f0
}

f2() {
  f1
}

#1
f2

# --- output ---


#1
f0 f1 f2 main
}}}

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

#geshi(bash){{{
# --- 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
}}}


関数定義の確認

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

function my1() {
  :
}

function my2() {
  :
}

typeset -f
typeset -F

# --- output ---

my1 () 
{ 
    :
}
my2 () 
{ 
    :
}
declare -f my1
declare -f my2
}}}
* 変数 [#vcdab9f8]
#geshi(bash){{{
name=[value]
}}}

- valueが指定されない場合は、null
- チルダ展開、変数展開、コマンド置換、算術展開、クオートremovalが行われる。単語分割、ファイル名展開は行われない。
- 整数が渡される場合は、数値として評価される

*** += [#ma5ad401]
&code(){+=};は、valueへの追加、配列の場合は要素追加する。&color(red){数値が設定されていても変数属性がintegerとなっていない場合は、単なる文字列結合となるので注意。};。~
変数が配列の場合は、+=で要素を追加すると末尾に追加される。
#geshi(bash){{{
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
}}}

*** 参照変数 [#o13130c3]

&code(){declare -n}; で参照変数を定義することができる。unset -n で参照を削除する。-nがない場合は、参照先の変数がunsetされる。配列変数は、変数参照指定ができないが、要素には可能である。
#geshi(bash){{{
# --- 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文で制御変数を参照設定して操作することも可能である。

#geshi(bash){{{
# --- 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
}}}

** 位置パラメータ [#ve953f26]

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

#geshi(bash){{{
# --- 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
}}}
** 特殊パラメータ [#d4230903]

''$*''

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

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

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

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


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

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

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

  # $1 $2 ...
  f2 $*

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

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

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

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

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

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

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

}

f a b c d

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

# --- output ---

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



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



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


''$@''

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

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

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

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

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

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

  # $1 $2 ...
  f2 $@

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

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

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

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

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

f a b c d

f3 "echo hello"
f4 "echo hello"

# --- output ---

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


''$#''

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

#geshi(bash){{{
# file: test.sh
echo $@
echo $#

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

# --- output ---

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

''$?''

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

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

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

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

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

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

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

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

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

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

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

# --- output ---


#1
0

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

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

#4: function f3 ends with exit status 0
0

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

''$-''

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

#geshi(bash){{{
set -x                                                                                                                                                              
echo $-                                                                                                                                                             
set +x                                                                                                                                                              
echo $-                                                                                                                                                             

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

+ echo hxB
hxB
+ set +x
hB
}}}

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

#geshi(bash){{{
$ echo $-
himBH
}}}

''$$''

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

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

''$!''

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

#geshi(bash){{{
$ sleep 30 &
[1] 15816
$ echo $!
15816
$ echo "hello"
hello
$ echo $!
15816
$ sleep 30 &
[2] 15870
$ echo $!
15870
$ jobs -l
[1]- 15816 Running                 sleep 30 &
[2]+ 15870 Running                 sleep 30 &
}}}

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

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

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

''$0''

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

#geshi(bash){{{
# hello.sh
echo $0

# ---------

./hello.sh    #=> ./hello.sh
bash hello.sh #=> hello.sh
}}}

#geshi(bash){{{
bash -c 'echo $0'       #=> bash
bash -c 'echo $0' hello #=> hello
}}}

#geshi(bash){{{
$ echo $0
bash
$ /bin/bash
$ echo $0
/bin/bash
$ ln -s /bin/bash hogehoge
$ ./hogehoge
$ echo $0
./hogehoge
}}}


''$_''

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

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

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

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

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

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

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

#4
f
echo $_

#5
f func_arg
echo $_

#6
./com.sh
echo $_

#7
./com.sh hello
echo $_

# --- output ---


#1
-G

#2

/bin/echo

#3
hello
/bin/echo hello

#4
func
f

#5
func_arg
func_arg

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

#7
./com.sh
hello
}}}

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

#geshi(bash){{{
$ _=hoge
$ echo $_

}}}
** 配列の操作 [#e4d1b819]

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

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

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

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

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

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

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

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

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

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

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

# --- output ---


#1: 代入

1 1
2

#2: 宣言
1

#3: 初期化
1 2 3 4

#4: 繰り返し
1
2
3
4

#5: 削除
1
2
3
4

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

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

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


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

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

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

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

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

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

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

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

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

# --- output ---


#1: 代入

1 1
2

#2: 宣言
1

#3: 初期化
1 2 3 4

#4: 繰り返し
1
2
3
4

#5: 削除
1
2
3
4

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

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

#8: インデックスで繰り返し
1
2
3
4
}}}
** 連想配列の操作 [#ye165ab2]

#geshi(bash){{{
declare -A name
}}}


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

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

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

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

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

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

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

# --- output ---


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

#2: 参照
120

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

#4: 繰り返し2
150
120
100

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

#6: 全削除
declare -- map
}}}
* 展開 [#vdd07017]

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

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

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

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

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

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

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

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

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

** チルダ展開 [#r8b69aa5]

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

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

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

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

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

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

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

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

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

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


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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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


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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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


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

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

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

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

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


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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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


&label(warn){参考}; https://www.gnu.org/software/bash/manual/bash.html#Shell-Parameter-Expansion
** コマンド置換 [#p8157dd3]

#geshi(bash){{{
$(command)
or
`command`
}}}

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

#geshi(bash){{{
# --- 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
}}}
** 算術展開 [#of170bf3]

#geshi(bash){{{
$(( expression ))
}}}

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

#geshi(bash){{{
# +++++++++++++++ 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
}}}

&label(rel){関連};  [[算術式>#Shell_Arithmetic]]
** プロセス置換 [#we760055]

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

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

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

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

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

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

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

echo "hello"
echo "bar"

exec 1>&3 3>&-
}}}

&label(warn){参考};
- [[bashのプロセス置換で遊んでみよう!>https://techblog.raccoon.ne.jp/archives/53726690.html]] - &size(11){&color(gray){on https://techblog.raccoon.ne.jp/archives/53726690.html};};
** 単語分割 [#sa8c290a]

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

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

#geshi(bash){{{
# +++++++++++++++ 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
}}}
** ファイル名展開 [#j7d55b04]

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

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

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


#geshi(bash){{{
# +++++++++++++++ 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
}}}

*** パターンマッチング [#s0fe7d16]

**** * [#c2b64ce8]

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

#geshi(bash){{{
$ 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
}}}

**** ? [#hd1e464e]

任意の1文字

#geshi(bash){{{
$ 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
}}}

**** […] [#t75701a6]

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

&code(){[!};、&code(){[^};はnot matchとなる。

#geshi(bash){{{
$ 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標準で定義されている文字クラスが使用可能。

#geshi(bash){{{
[:class:]

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

&code(){word};は、文字、数値、_にマッチする。

#geshi(bash){{{
$ 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=]は、 è、 é、 ê、 ë などにもマッチする。

#geshi(bash){{{
$ 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.])''

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

#geshi(bash){{{
$ 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
}}}

&label(warn){参考}; 
- http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap07.html#tag_07_03_02_04
- https://stackoverflow.com/questions/42399455/what-the-mean-of-c-and-symbol-in-bash
- https://www.regular-expressions.info/posixbrackets.html

#hr

*** extglobで有効なパターンマッチング [#m6fabc9f]

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

#geshi(bash){{{
shopt -s extglob
}}}

pattern-listには、&code(){ | };で区切った1つ以上のパターンが入る。

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

**** ?(pattern-list) [#x3d1c718]

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

#geshi(bash){{{
# +++++++++++++++ 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) [#b9b0d58a]

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

#geshi(bash){{{
# +++++++++++++++ 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) [#pfb4b86f]

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

#geshi(bash){{{
# +++++++++++++++ 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) [#adba6ea0]

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

#geshi(bash){{{
# +++++++++++++++ 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) [#zce89a3d]

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

#geshi(bash){{{
# +++++++++++++++ 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
}}}
* リダイレクション [#q2763390]

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

** 入力のリダイレクト [#gbcedc6c]

#geshi(bash){{{
[n]<word
}}}

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

#geshi(bash){{{
# +++++++++++++++ Commands +++++++++++++++
echo "line1
line2" > filename.out

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

# ***** <[Output]> ***** 
line1
line2
}}}
** 出力のリダイレクト [#p304fa93]

#geshi(bash){{{
[n]>[|]word
}}}

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


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

#geshi(bash){{{
# +++++++++++++++ [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
}}}
** 追記型出力のリダイレクト [#re097662]

#geshi(bash){{{
[n]>>word
}}}

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

#geshi(bash){{{
# +++++++++++++++ [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
}}}


** 標準出力と標準エラー出力のリダイレクト [#uf8294b7]

#geshi(bash){{{
&>word
>&word

以下と等価

>word 2>&1
}}}

#geshi(bash){{{
# +++++++++++++++ [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
}}}
** ヒアドキュメント [#oc115994]

#geshi(bash){{{
[n]<<[-]word
        here-document
delimiter
}}}

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

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

&code(){<<-};が使われる場合、全ての行頭のタブ文字は取り除かれる。

#geshi(bash){{{
# +++++++++++++++ [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)
		$param
		$(pwd)
STRING

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


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

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

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

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


** ヒアストリング [#h8ef0909]

#geshi(bash){{{
[n]<<< word
}}}

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

#geshi(bash){{{
# +++++++++++++++ [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
}}}
** ファイルディスクリプタの複製 [#t559ed6e]

** ファイルディスクリプタの移動 [#wcde26fa]
#geshi(bash){{{
# 入力
[n]<&word
# 出力
[n]>&word
}}}

** 読み書き可能なファイルディスクリプタ [#k7862f72]
** execを使った入出力のリダイレクト [#s12f0ef4]
- &code(){word};が数値ならば、記述子nは&code(){word};のコピーになる。
- &code(){word};がオープンされた記述子でない場合はエラーとなる。
- &code(){word};が&code(){ - };の場合、記述子nはクローズされる。

*** 標準入力の複製 [#ie852fb6]

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

#geshi(bash){{{
# ++++++++++++++++++++++ [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
}}}

*** 標準出力の複製 [#s5196b63]

#geshi(bash){{{
# ++++++++++++++++++++++ [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
}}}


*** その他サンプル [#zf844f88]

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

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

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

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

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

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

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

exec > test.txt

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

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

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

#geshi(bash){{{
:> 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
2020年 1月 1日 水曜日 23時26分25秒 JST: received: job1
2020年 1月 1日 水曜日 23時26分28秒 JST: received: job1
2020年 1月 1日 水曜日 23時26分29秒 JST: received: job1

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

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

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

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

# 複合コマンド
{
  # stdout.txtに出力
  echo "this message is written into stdout.txt"
  # stderr.txtに出力
  echo "this message is written into stderr.txt" >&2
} > stdout.txt 2> stderr.txt
}}}
** ファイルディスクリプタの移動 [#wcde26fa]

#geshi(bash){{{
# 入力
[n]<&digit-
# 出力
[n]>&digit-
}}}

&code(){digit};の記述子が&code(){n};に複製された後閉じられる。

** 読み書き可能なファイルディスクリプタ [#k7862f72]

#geshi(bash){{{
[n]<>word
}}}

#geshi(bash){{{
# ++++++++++++++++++++++ [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
}}}

* シェル組み込みコマンド [#ua8102c6]

* シェル変数 [#w52efc1c]

** Bourne Shell変数 [#x66409a9]

** Bash変数 [#o427deff]

* Bashの機能 [#od873a7b]

* ジョブ制御 [#ba8a6efd]

* コマンドライン編集 [#x9876060]

* コマンド履歴 [#p2a072ac]

* 参考リンク [#q8d32fb3]
- [[(PR) 入門bash 第3版>http://www.amazon.co.jp/exec/obidos/ASIN/4873112540/grepgrape-22/ref=nosim/]] - &size(11){&color(gray){on http://www.amazon.co.jp/exec/obidos/ASIN/4873112540/};};

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

- [[Bash $((算術式)) のすべて - A 基本編>https://qiita.com/akinomyoga/items/9761031c551d43307374]] - &size(11){&color(gray){on https://qiita.com/akinomyoga/items/9761031c551d43307374};};
- [[The Bash Hackers Wiki>https://wiki.bash-hackers.org/start]] - - &size(11){&color(gray){on https://wiki.bash-hackers.org/start};};


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