技術者になりたい何か

技術者になりたい何かの覚書的な何かです

そのshの正体は

busyboxのshはashで配列が使えないという話です。

業務でシェル芸的ワンライナーを作ることが多くなってきてたのですが、どハマリしたのがshがbashではない環境。
具体的にはESXiのデフォルトシェルはどうやらbusyboxのashらしく、配列が使えないという話。

■そのシェルなんでしょう

今使ってるシェル、というかshで呼び出されるシェルはwhichとlsで分かるようです。
・Archの場合

$ which sh
/usr/bin/sh
$ ls -l /usr/bin/sh
lrwxrwxrwx 1 root root 4  64 17:54 /usr/bin/sh -> bash
$ ls -l /usr/bin/bash
-rwxr-xr-x 1 root root 866600  64 17:54 /usr/bin/bash

shは/usr/bin/shで、この実体はbashへのシンボリックリンクになってます。
念の為/user/bin/bashをls -lしてみると、こちらは実行権限付きのファイルになってるので、これが実体。

Debian(9.5)の場合

$ which sh
/bin/sh
$ ls -l /bin/sh
lrwxrwxrwx 1 root root 4  124  2017 /bin/sh -> dash
$ which sh
/bin/sh
$ ls -l /bin/dash
-rwxr-xr-x 1 root root 117208  124  2017 /bin/dash

同じように調べてみると、shは/bin/shを呼び出して、/bin/shはdashへのシンボリックリンクになっています。
念の為/bin/bashをls -l してみると、こちらが実行権限付きのファイルになっています。

ちなみにDebianでもbashは入ってるので、明確にbashを指定すればbashでしか通じない文法のシェルでも問題なく動くでしょう。

$ ls -l /bin/dash
-rwxr-xr-x 1 root root 117208  124  2017 /bin/dash

実はシェルは他にもいろいろあるので、気になる人はこの辺見てください。(ちょっと情報古いけど)
シェル

■実は配列が使えないシェルがある

これがドハマったところでした。
実はシェルの配列ってbashの機能だったんですね。

具体的にはESXiのコマンドラインで、コマンド結果を配列に突っ込んで、順に取り出しながら代入してうんたらかんたら、みたいなことをワンライナーで書いてたときにそれは起こりました。
sh: syntax error: unexpected "("
シンタックスエラー??
書き方が悪いと思って調べながら何度修正してもだめ。というか、以前にRedHatLinux上で動かしたワンライナー配列のコマンドをもとにして書いてたので、本当に迷宮入りしそうでした。
が、実はESXiのシェルって/bin/busybox ashが実体だったらしいです。(busyboxのデフォルトシェルはashらしい。)


dash,bash,busyboxすべて入っているDebianで試してみましょう。
ashに変更

tmin@tminserver:~$ /bin/busybox ash


BusyBox v1.22.1 (Debian 1:1.22.0-19+b3) built-in shell (ash)
Enter 'help' for a list of built-in commands.

~ $ 

さてこれで配列が使えるかというと

~ $ array=(a b c d) ; for i in ${array[@]} ; do echo $i ;done
sh: syntax error: unexpected "("

だめです。

bashに戻して配列を使うワンライナーを流す。配列をa b c d として、一個ずつ取り出してechoするだけのワンライナー

~ $ /bin/bash
tmin@tminserver:~$ array=(a b c d) ; for i in ${array[@]} ; do echo $i ;done
a
b
c
d

これが想定の動きです。

dashならどうか?どうやらdashはashがベースらしいですが。

tmin@tminserver:~$ /bin/dash
$ array=(a b c d) ; for i in ${array[@]} ; do echo $i ;done
/bin/dash: 1: Syntax error: "(" unexpected

だめですね。

つまり#!/bin/sh ではなく#!/bin/bash って書いとけばシェルなら大丈夫。ワンライナーなら確実にbashに切り替えてから実行すればおk。
でもDebianと違って、ESXiのデフォルトではbashがないのですよ!!

■解決法

1.配列+forによる繰り返しをやめてwhileでなんとかする。
何も根本的解決になってないですけど。コマンド結果を配列に入れて、一個ずつ取り出して代入してうんたらかんたら、っていうくらいのことならwhile使えばいいよ。
※問題点
複数の配列を使いたかった処理の場合これでは目的を達成できない。
達成しようとした場合、一撃で終わらせたかった処理が2撃以上になるか、シェルが冗長になるかどちらか。
とはいえ、今回自分がハマっていた処理についてはこれで行けた。(lsの結果を配列に突っ込んでうんたらかんたら、みたいなのだったと記憶している)

2.set -- $arrayを使う
https://stackoverflow.com/questions/26091758/arrays-in-shell-script-not-bashstackoverflow.com

さすがStack Over Flow。

$ array="0 1 2 3" ; set -- $array ; for i in $array ; do echo $i ;done
0
1
2
3
~ $ 

でも複数配列うまく使うのはまた難しそう。

■結論

bashない環境はきっつい。
なんで動かないのかよくわからないエラーが出てるときはそれほんとにbashか疑うのもときに必要。
あといろんなシェルでビミョーに挙動が違うのってたまにしかでくわさないしむずかしーです。