けしからんシェルスクリプトを書き直す

ブログ

こんにちは、山田ハヤオです。

シェルスクリプトはかなり移植性が高く、OSに依存せずスクリプトを実行できます。

また、書き方も自由度が高いので(少しだけ注意すれば)自由な書き方ができます。

しかし、環境によっては動かなかったり、誤作動を起こすけしからんコードを書いてしまう場合もあります。

そういったシェルスクリプトを警告してくれるのがshellcheckというツールです。

今回はよく見かけるけしからん実装を直していきたいと思います。

スポンサーリンク

例1(変数とechoの引数)

function HelloWorld () {
    str=helloworld
    echo $str
}

この関数にはあまり良いとは言えない箇所が最低でも3つあります。

(個人的に)良いと思うのはこのコード。

function HelloWorld () {
    local str="helloworld"
    echo "${str}"
}

シェルスクリプトはデフォルトの変数のスコープがグローバルなので、関数内でのみ使う場合はローカル変数であることを明示的に宣言したほうがきれいです。

文字列は""で囲い、変数を参照する場合は$strの代わりに"${str}"を使ったほうが良いです。

例えばこの文字列に空白が入っていた場合、最初のコードはエラーになります。

また、echoコマンドの引数を引用符で囲うかどうかで引数の解釈の仕方が変わります。

例2(コマンドライン引数を動的に設定する)

mode2="true"
opts="-a ARGS -f 1 -g"

if [[ "${mode2}" ]]; then
    echo "Enabled Mode2"
    mode2="${mode2} -x"
fi

_my_command ${opts}

とあるコマンドライン引数を変数の値によって変化させるコードです。

昔の私はよくこれをしていたのですが重大なバグがあります。

この_my_commandへの引数として-a ARGS -f 1 -gが渡されています。

-aのパラメータとしてARGSが渡されていますが、もし-aHello Worldというオプションを渡そうとすると正常に解釈されなくなります。(HelloとWorldの間の空白のせいで誤作動を起こします)

これの対策のためにコマンドライン引数を代入する場合は変数ではなく配列を使います。

mode2="true"
opts=("-a" "Hello World" "-f" "1" "-g")

if [[ "${mode2}" ]]; then
    echo "Enabled Mode2"
    mode2+=("-x")
fi

_my_command "${opts[@]}"

配列を使用すると空白などの文字列を適切に処理できるようになります。

例3(コマンドの実行結果を配列に代入する)

デスクトップのファイルの一覧を配列に代入する処理です。

desktop_files=($(find ~/Desktop))

このコードにもいくつも問題点があります。

まず、ホームディレクトリは~/ではなく${HOME}を使うべきです。

また、英語以外のユーザーではデスクトップへのパスは~/Desktopとは限りません。

ArchWikiによると以下のコマンドでデスクトップへのパスを取得できます。(${HOME}は使いませんでしたね

xdg-user-dir DESKTOP

それを踏まえてコードを修正します。

desktop_files=($(find "$(xdg-user-dir DESKTOP)"))

これでもまだ問題があります。findでファイル一覧を探すと自分自身も含まれてしまいます。

find /home/hayao/Desktopを実行すると/home/hayao/Desktop自身も実行結果に含まれてしまいます)

なので最低限のファイル階層を修正する必要があります。

desktop_files=($(find "$(xdg-user-dir DESKTOP)" -,mindepth 1))

このコードでは、もしファイル名に空白が含まれていた場合、分割されてしまい適切に配列に代入されません。

もし「file1.mp3」「file2.xls」「file3.mp4」「another file.doc」という4つのファイルがあるとします。

その場合のこのコマンドの実行結果はだいたいこんな感じになります。JavaScriptで表すとこれ。

const desktop_file = [ 'file1.mp3', 'file2.xls', 'file3.mp4', 'another ', 'file.doc' ]

正しい配列ではこうなるはずです。

const desktop_file = [ 'file1.mp3', 'file2.xls', 'file3.mp4', 'another file.doc' ]

配列で適切に空白を扱うにはreadarrayコマンドを使います。

readarrat -t desktop_files < <(find "$(xdg-user-dir DESKTOP)" -mindepth 1)

readarrayの標準入力にfindの実行結果を渡してあげます。

そうすることで適切に配列を代入することができます。

終わりに

よくやってしまう間違ってる(と個人的に考えている)シェルスクリプトの書き方を紹介しました。

こうすることでバグを減らすことができるので参考にしてみてください。

それでは、また今後。

コメント

タイトルとURLをコピーしました