echo("備忘録");

IT技術やプログラミング関連など、技術系の事を備忘録的にまとめています。

【Linux】ワイルドカードを変数に代入する方法

おととい(2018/01/22)は全国的に大雪で、東京や関東で、ものすごい数の人が駅で立ち往生を食らっている、という映像をニュースやSNSで見ました。

幸い、愛知県平野部は雪の影響はなかったですが、あの人だかりを見ると、やはり東京(というか関東)は僕にとって「(リモート等で)仕事する場所」ではあっても、「住む場所」ではないな、と改めて感じました。


で、本題

例えば、下記のような内容のテキストファイルから「ファイルパス&ファイル名を1行ずつ読み込んで、何か処理する」とします。

/mnt/c/users/makky12/,sample.txt
/mnt/c/users/makky12/,*
...

で、これを

while read line; do
    ##${line}は「ファイル一行の内容」。
    declare -a array
    array=(`echo ${line} | tr ',' ' '`)
    dir=$array[0]
    file=$array[1]
    echo "dir=${dir}"
    echo "file=${file}"
done < ${sample} 

とすると、例えばテキストの2行目の内容なら普通、${file}には'*'が入ると思いますよね。
ところが、実際は、

dir='/mnt/c/users/makky12/'
file='abc.txt'  ##←なにこれ?

ということが起こります。

僕も最初、意味が全く分からなかったんですが、色々調べてみて、ようやく分かりました。


ワイルドカードはまず展開される

と、いきなり結論を書きましたが、要はワイルドカード文字(「*」「?」など)があると、それを「文字列」ではなく「ワイルドカード」として認識してしまうので、この場合「(カレントディレクトリの)条件に一致するファイル」を取得してしまいます。(要は下記と同じ意味)

file=`ls *`

なので先述の例では「カレントディレクトリ内にあるファイル名」が変数fileに代入されてしまうのです。

※これは各種コマンドの引数に「ワイルドカードでファイル名を指定した場合」も同じです。例えば'cp'コマンドの場合

cp /mnt/c/users/makky12/* /mnt/c/users/makky12/sample/
## 例えば'a.txt,b,txt,c.txt'の3つがあった場合、下記と同じような意味になる。
cp [/mnt/c/users/makky12/a.txt /mnt/c/users/makky12/b.txt /mnt/c/users/makky12/c.txt] /mnt/c/users/makky12/sample/

なので、条件に一致した全ファイルを処理対象としてくれるわけですが、上記のように該当するファイルをすべて引数に設定してしまうので、

if [ ${#} -gt 2 ]; then
    echo "引数は2つまでです。"
    exit 1
fi

declare file=${1}
declare mode=${2}

上記のような処理があるシェルスクリプトで、

bash 〇〇.sh /mnt/c/users/makky12/* -r

と指定すると、一致したファイル数によっては(「〇〇.sh」以外の)引数が2個以上、となり「引数は2つまでです」と表示されてしまいます。


対策方法

■1:""で囲む

先程の例では$array[1]を「""」で囲みませんでしたが、これを

file="$array[1]"

と、「""」で囲ってあげると、ワイルドカード文字であっても、正しく変数fileに代入されます。

■2:'set -f'を使用する

'set -f'コマンドは「ワイルドカードの自動展開を無効にする」オプションです。
これを利用して

set -f
file=$array[1]
set +f

とすれば、やはりワイルドカード文字であっても、正しく変数fileに代入されます。
ただし、当然'set -f'コマンド実行後はずっと「ワイルドカードの自動展開が無効」なので、使用後はすぐに'set +f'コマンドで元に戻さないと、予期せぬバグの原因になったりするので、要注意です。

■ちなみに...
上記の処理をしても、僕はまだ不安なところがあったので、実際のソースでは

set -f
file=`echo "$array[1]"`
set +f

としました。(意味があるかは不明ですが、念のため)

・先述の例は「変数に代入する場合」の話ですが、直接コマンド(の引数など)で実行する場合、先述の方法の他に

## 「''」で囲む方法
$ echo '*'

## 「\」を使う方法
$ echo \*

なども使用できます。

参考URL:
qiita.com
・sh での変数とワイルドカードの落とし穴(https://www.ecoop.net/memo/archives/2006-02-02-1.html


シェルスクリプトは便利な反面、こういう一見分かりにくい動作をする事もあるので、気を付けなくてはなりませんね。


終わり!閉廷!以上!解散!