【シェルスクリプトツール作成の基本】文字列をシェルコマンドとして実行するアレコレ


※ 当ページには【広告/PR】を含む場合があります。
2024/01/04
【シェルスクリプトツール作成の基本】自作のシェルスクリプトでパイプを使えるようにする

自身お久しぶりのシェルスクリプトの基礎的な内容の話です。

今回は、たまに使いたいときのあるシェルから
「文字列をコマンドで実行する」という、シンプルな割に、知らないと沼にはまるのもしばしばど忘れする感じの内容を防備録としても残しておきましょう。


合同会社タコスキングダム|蛸壺の技術ブログ【効果的学習法レポート】シェルスクリプトをこれから学びたい人のためのオススメ書籍&教材特集

文字列をシェルコマンドで実行するテクニック

一度に手で入力するには大変なくらい大量の引数を要求されるコマンドをワンライナーで実行する場合、一旦コマンドを文字列として生成してから、その文字列を実行させると便利なときがあります。

文字列をコマンドとして実行させる方法はいくつかあり、例えば以下のように
1.shというスクリプトを実行してみます。

            
            #!/bin/bash

script='echo HELLO!'

#👇文字列をそのまま打ち出すと自動でコマンドと判別してくれるケース
${script}

#👇[`]文字で括ってechoコマンドで文字列を標準出力で打ち出すと文字列をコマンドと判別してくれるケース
`echo ${script}`

#👇理屈は上と同じで$()を使ってechoコマンドを実行しているケース
$(echo ${script})

#👇eval関数を利用するケース
eval ${script}
        

            
            $ chmod +x 1.sh
$ ./1.sh
HELLO!
HELLO!
HELLO!
HELLO!
        
一見上記の4通りの方法は簡単なコマンドの文字列だとほぼ違いがないように見えますが、実は細かな違いが存在しています。

この微妙な違いを理解するため、すこし実験してみます。

先ほどの
1.shを改造して、以下のような2.shを作って実行してみます。

            
            #!/bin/bash

script='hoge runs a function!'

hoge() {
    #👇$0以外のコマンドライン引数
    echo $*
    #👇コマンドラインの引数の数
    echo $#
}

${script}
`echo ${script}`
$(echo ${script})
eval ${script}
        

            
            $ ./2.sh
runs a function!
3
runs a function!
3
runs a function!
3
runs a function!
3
        
今回のスクリプトは一つの引数('runs a function!'の文字列)付きの関数hogeを実行するの文字列を走らせたつもりでも、3つの引数('runs' / 'a' / 'function!')があるように解釈されてしまいます。

どうやら空白文字があるために、hoge以降の文字列は全て個別の引数だと認識されているようです。

今度は、明示的に
"で括って文字列であることを教えてあげるとどうでしょうか。

            
            #!/bin/bash

script='hoge "runs a function!"'

hoge() {
    echo $*
    echo $#
}

${script}
`echo ${script}`
$(echo ${script})
eval ${script}
        

            
            $ ./2.sh
"runs a function!"
3
"runs a function!"
3
"runs a function!"
3
runs a function!
1
        

今度は、最初の3つのやり方はやはり引数が3つであると解釈されています。

evalを用いたときだけは正しく文字列を認識しているようです。

ちなみにダブルクォート
"とシングルクォート'を入れ替えても結果は一緒です。

            
            #!/bin/bash

script="hoge 'runs a function!'"
#👇のように\"を使っても一緒
#script="hoge \"runs a function!\""

hoge() {
    echo $*
    echo $#
}

${script}
`echo ${script}`
$(echo ${script})
eval ${script}
        

            
            $ ./2.sh
'runs a function!'
3
'runs a function!'
3
'runs a function!'
3
runs a function!
1
        
これらの結果を通して分かるように、eval以外のやり方は結局コマンド 引数1 引数2 ...というように空白文字を挟んで単純な解釈でしかコマンドを実行してくれないのに対し、evalは渡された文字列をコマンドの表現式だと捉え、よりスマートな評価を行いコマンドに翻訳してくれています。

以下はdate関数を利用した、
eval以外では思うように実行されないけれどevalなら実行されるケースを何パターンかやってみます。

            
            #!/bin/bash
echo '========= Execute a script ========='
a="date"
aarg="--date='TZ=\"America/Los_Angeles\" 09:00 next Fri'"
a="${a} ${aarg}"
${a}
`echo ${a}`
$(echo ${a})
eval ${a}

echo '========= Execute b script ========='
b="date '+%Y-%-m-%-d'"
${b}
`echo ${b}`
$(echo ${b})
eval ${b}

echo '========= Execute c script ========='
c="date \"+%Y-%-m-%-d\""
${c}
`echo ${c}`
$(echo ${c})
eval ${c}

echo '========= Execute d script ========='
darg='+%Y %-m %-d'
d="date '${darg}'"
${d}
`echo ${d}`
$(echo ${d})
eval ${d}

echo '========= Execute e script ========='
earg='+%Y %-m %-d'
e="date \"${earg}\""
${e}
`echo ${e}`
$(echo ${e})
eval ${e}
        

            
            $ ./3.sh
========= Execute a script =========
date: 余分な演算子 `next'
Try 'date --help' for more information.
date: 余分な演算子 `next'
Try 'date --help' for more information.
date: 余分な演算子 `next'
Try 'date --help' for more information.
2020年 10月 10日 土曜日 01:00:00 JST
========= Execute b script =========
date: `\'+%Y-%-m-%-d\'' は無効な日付です
date: `\'+%Y-%-m-%-d\'' は無効な日付です
date: `\'+%Y-%-m-%-d\'' は無効な日付です
2020-10-2
========= Execute c script =========
date: `"+%Y-%-m-%-d"' は無効な日付です
date: `"+%Y-%-m-%-d"' は無効な日付です
date: `"+%Y-%-m-%-d"' は無効な日付です
2020-10-2
========= Execute d script =========
date: 余分な演算子 `%-m'
Try 'date --help' for more information.
date: 余分な演算子 `%-m'
Try 'date --help' for more information.
date: 余分な演算子 `%-m'
Try 'date --help' for more information.
2020 10 2
========= Execute e script =========
date: 余分な演算子 `%-m'
Try 'date --help' for more information.
date: 余分な演算子 `%-m'
Try 'date --help' for more information.
date: 余分な演算子 `%-m'
Try 'date --help' for more information.
2020 10 2
        

見てのように、
eval関数を使ったケースが文字列からのコマンド実行をもらさず処理できていることがわかります。


合同会社タコスキングダム|蛸壺の技術ブログ【効果的学習法レポート】シェルスクリプトをこれから学びたい人のためのオススメ書籍&教材特集

まとめ

ちょっと回りくどかったようですが、実験の結論としては、文字列からのコマンド実行はeval関数を使いましょう。
記事を書いた人

記事の担当:taconocat

ナンデモ系エンジニア

主にAngularでフロントエンド開発することが多いです。 開発環境はLinuxメインで進めているので、シェルコマンドも多用しております。 コツコツとプログラミングするのが好きな人間です。

合同会社タコスキングダム|蛸壺の技術ブログ【効果的学習法レポート】シェルスクリプトをこれから学びたい人のためのオススメ書籍&教材特集