試験運用中なLinux備忘録・旧記事

はてなダイアリーで公開していた2007年5月-2015年3月の記事を保存しています。

bashとzshでの配列の添え字と判別について

zshというシェルはbash互換の部分が多いのだが、配列の要素を個別に参照するときの番号(添え字)が異なることが分かった。

zshの配列は1番から

ARRAY=("aaa" "bbb" "ccc" "ddd" "eee")
printf "0:%s 1:%s 2:%s 3:%s 4:%s 5:%s\n" ${ARRAY[0]} ${ARRAY[1]} ${ARRAY[2]} ${ARRAY[3]} ${ARRAY[4]} ${ARRAY[5]}

このスクリプトを実行すると

(bashの場合)
0:aaa 1:bbb 2:ccc 3:ddd 4:eee 5:
(zshの場合)
0:aaa 1:aaa 2:bbb 3:ccc 4:ddd 5:eee

となり、zshでの番号がずれている(1番からになる)ことが分かる。
(2009/4/19)トラックバックを頂いて、相手の方のコメントが気になったので、再び実験を行った。まず、上のスクリプトを実行してみたところ、両方とも

0:aaa 1:bbb 2:ccc 3:ddd 4:eee 5:

となってしまった。バージョンは

$ bash --version
GNU bash, version 3.2.19(2)-release (x86_64-mandriva-linux-gnu)
Copyright (C) 2005 Free Software Foundation, Inc.
$ zsh --version
zsh 4.3.6 (x86_64-mandriva-linux-gnu)

となっている。次に
ファイル名: test.sh

ARRAY=("aaa" "bbb" "ccc" "ddd" "eee")
printf "0:[%s] 1:[%s] 2:[%s] 3:[%s] 4:[%s] 5:[%s]\n" ${ARRAY[0]} ${ARRAY[1]} ${ARRAY[2]} ${ARRAY[3]} ${ARRAY[4]} ${ARRAY[5]}
echo "[${ARRAY[0]}|${ARRAY[1]}|${ARRAY[2]}|${ARRAY[3]}|${ARRAY[4]}|${ARRAY[5]}]"

のようにしてもう少し詳しく調べてみた。

$ bash test.sh
0:[aaa] 1:[bbb] 2:[ccc] 3:[ddd] 4:[eee] 5:[]
[aaa|bbb|ccc|ddd|eee|]
$ zsh test.sh
0:[aaa] 1:[bbb] 2:[ccc] 3:[ddd] 4:[eee] 5:[]
[|aaa|bbb|ccc|ddd|eee]

zshにおけるprintfでの出力のされ方はやはり変わっている。また、0番の項目を取り出そうとしても空で取り出せないという違いもあった。個別に取り出してみると、添え字が1番からなのは変わらなかった。なお
ファイル名: test2.sh

ARRAY=("aaa" "bbb" "ccc" "ddd" "eee")
for X in ${ARRAY[*]}; do
  echo ${X}
done

for文での処理では番号のずれを気にせず書けることも分かった。

$ bash test2.sh        
aaa
bbb
ccc
ddd
eee
$ zsh test2.sh
aaa
bbb
ccc
ddd
eee

bashzshの判別例

下のコードでは、bashもしくはzshを使用しているとき*1に、その名前を表示する。他のシェルで実行すると「unsupported shell」を表示する。

if test -n "${BASH_VERSION:-}"; then
  echo bash
else
  if test -z "${ZSH_VERSION:-}"; then
    echo "unsupported shell"
    exit 1
  else  # 配列の添え字に関する処理ではzsh用の処理はなく、else節は書かない
    echo zsh
  fi
fi

このコードではzshの場合にelse節の処理が実行されるが、下で紹介しているコードのように、「zshの場合には何もしない(else節を書かない)」という場合があるため、この形で記述した。

判別と要素ずらしを利用して、bash/zshの両方で同様に動作するようにする

bash/zshの両方で同じように動作させたいコードがあってその中で配列を使用する場合の解決策の1つとして、「bashのときに配列の0番に無意味なデータを入れて、要素を1ずつ後ろにずらす」という方法がある。
下のコードは、bash上でもzsh上でも同様の結果を出力する。

ARRAY=("aaa" "bbb" "ccc" "ddd" "eee")
if test -n "${BASH_VERSION:-}"; then
  # bashのときに配列全体を1番からにずらす
  ARRAY=("" ${ARRAY[@]})
else
  if test -z "${ZSH_VERSION:-}"; then
    echo "unsupported shell"
    exit 1
  fi
fi
# 個別に取り出し、同じように動作することを確認
printf "1:%s 2:%s 3:%s 4:%s 5:%s\n" ${ARRAY[1]} ${ARRAY[2]} ${ARRAY[3]} ${ARRAY[4]} ${ARRAY[5]}
# 各要素についての反復処理も同様に動作
for X in ${ARRAY[*]}; do
  echo ${X}
done

実行結果

1:aaa 2:bbb 3:ccc 4:ddd 5:eee
aaa
bbb
ccc
ddd
eee

関連記事:

使用したバージョン:

*1:bash [スクリプト名]」のように、シェルの引数にスクリプトを指定して実行した場合、そのシェル上で動作する。スクリプトを直接実行した場合は最初の行の実行ファイル指定部分(shebang)に書かれたシェルが使用される。他には、動作中のシェルから.もしくはsourceで参照するという形もある