rcmdnk's blog

zshの本 (エッセンシャルソフトウェアガイドブック)

Bashだと、配列要素をunset arr[1]みたいに削除出来るわけですが、 Zshだとunsetは配列の各要素には使えない様でちょっと困った時の話。

Bashで配列の要素を削除

BashでもZshでもunsetを使って定義した変数を削除出来ますが、 Bashだと、配列の各要素も削除できます。

$ arr=(a "b c" "d e" f)
$ echo ${#arr[@]}
4
$ for i in {0..3};do echo "arr[$i]=${arr[$i]}";done
arr[0]=a
arr[1]=b c
arr[2]=d e
arr[3]=f
$ unset arr[2]
$ echo ${#arr[@]}
3
$ for i in {0..3};do echo "arr[$i]=${arr[$i]}";done
arr[0]=a
arr[1]=b c
arr[2]=
arr[3]=f
$ arr=("${arr[@]}")
$ for i in {0..3};do echo "arr[$i]=${arr[$i]}";done
arr[0]=a
arr[1]=b c
arr[2]=f
arr[3]=

ただし、削除するとその番号の要素が消えるだけで番号は詰められません。 詰めるためにはarr=("${arr[@]}")的なことをして詰めなおして上げる必要があります 1

Zshで配列の要素を削除

Zshだと配列の各要素に対してはunsetは使えません。

$ arr=(a "b c" "d e" f)
$ for i in {1..4};do echo "arr[$i]=${arr[$i]}";done
arr[1]=a
arr[2]=b c
arr[3]=d e
arr[4]=f
$ unset arr[3]
zsh: no matches found: arr[3]

こんな感じエラーになります 2

ちょっと調べた感じ直接消す方法がなさそう?だったんですが、 こんな感じでやったら上手くいきました。

$ arr=(a "b c" "d e" f)
$ echo ${#arr}
4
$ n=3
$ arr=(${arr[1,((n-1))]} ${arr[((n+1)),-1]})
$ echo ${#arr}
3
$ for i in {1..4};do echo "arr[$i]=${arr[$i]}";done
arr[1]=a
arr[2]=b c
arr[3]=f
arr[4]=

Zshだと${arr[i,j]}の形でi~jの要素を取り出せるので、 nより前と後をそれぞれ置いてるだけです。

ちょっと引っかかった所は、最初、前後の二つのそれぞれを"で 囲ってやろうとしたら全体が1つとして認識されてしまった点。

上の様に"は無しできちんと中身が(b cなども)分割されます。

Zshの場合は@/*などの時も"無しできちんとこの辺分割されます。 *を使った時に"で囲った時だけ全体が1つとして捉えられます。

${arr[i,j]}みたいにした場合は*と同じ様な扱いになるみたいで、 囲ってしまうと全体が1つとして捉えられてしまいます。

また、Bashで$arrみたいにすると0番目の要素が参照されますが、 Zshだと${arr[*]}とした時と同じ様になるみたいです(少なくとも下の例では)。

$ arr_orig=(a "b c" "d e" f)
$ arr=("${arr_orig[@]}")
$ for i in {1..4};do echo "arr[$i]=${arr[$i]}";done
arr[1]=a
arr[2]=b c
arr[3]=d e
arr[4]=f
$ arr=(${arr_orig[@]})
$ for i in {1..4};do echo "arr[$i]=${arr[$i]}";done
arr[1]=a
arr[2]=b c
arr[3]=d e
arr[4]=f
$ arr=(${arr_orig[*]})
$ for i in {0..3};do echo "arr[$i]=${arr[$i]}";done
arr[1]=a
arr[2]=b c
arr[3]=d e
arr[4]=f
$ arr=("${arr_orig[*]}")
$ for i in {1..4};do echo "arr[$i]=${arr[$i]}";done
arr[1]=a b c d e f
arr[2]=
arr[3]=
arr[4]=
$ arr=("${arr_orig[*]}")
$ for i in {1..4};do echo "arr[$i]=${arr[$i]}";done
arr[1]=a
arr[2]=b c
arr[3]=d e
arr[4]=f
$ arr=("$arr_orig")
$ for i in {1..4};do echo "arr[$i]=${arr[$i]}";done
arr[1]=a b c d e f
arr[2]=
arr[3]=
arr[4]=
$ arr=($arr_orig)
$ for i in {1..4};do echo "arr[$i]=${arr[$i]}";done
arr[1]=a
arr[2]=b c
arr[3]=d e
arr[4]=f

Bash/Zshで文字列の部分取得

配列のついでに文字列の部分取得について。

Bashでは少なくとも手元で一番古い3.2.25では

$ word="abcdefg"
$ echo ${word: 2: 3}
cde

とすれば2番目(最初の文字は0番)から3つの文字が表示されます。

同じくZshでもVersionが5以上であれば(取り敢えず手元の5.0.2では) 同じ事が出来ます。

ただ、古い4のバージョンの場合

zsh: unrecognized modifier `2'

と言ったエラーになってしまいます。

この場合、上の配列で使った部分取得の方法で

$ echo ${word[3,5]}
cde

この様にすれば同じ値が取れます。この場合は3番目から5番目まで、の取得で、 さらに最初の文字が1番なので上のBashの場合と区別が必要です。

ちょっと古い場所で使う場合には注意。

Sponsored Links
  1. 蛇足ですが、()内では ""で囲われた要素("b c"など)が正しく1つとして 扱われる様に*でなく@を使ってさらに全体を"で囲う必要があります。

    *を使えば外側の"で囲われた部分が全体が1つとして、 外側の"が無ければ@でも*でも中の要素が空白(もしくはIFS)で切られるので bとcが別要素になってしまいます。

  2. Zshの場合は通常配列が1から始まります:

    BashとZshの違いでのハマりどころ

    それから、上の例ではBash同様${arr[$i]}みたいに使ってますが、 Zshではこの{}を消して$arr[$i]と書くことも出来ます。

    ${#arr}$#arrでOK。

Sponsored Links

« シェルスクリプトでif文をANDで書き換える時の注意 シェルスクリプトでパイプからの入力とキーボード入力を区別する »

}