やってみる

アウトプットすべく己を導くためのブログ。その試行錯誤すらたれ流す。

シェルのHelloWorld!!における罠

 プログラミング言語を学ぶとき最初にやるヤツ。だがシェルはここにも罠がある。

HelloWorld!!

 まずは成功させる。

$ echo 'Hello world!!'
Hello world!!

 次に以下を打つと……謎の表示が出る。

$ echo "Hello world!!"
echo "Hello worldecho 'Hello world!!'"
Hello worldecho 'Hello world!!'

 なんじゃこりゃ? じつは以下のような知識が必要。

  1. !は「ヒストリ展開」機能である
  2. ''''と""の違い

!は「ヒストリ展開」機能である

 諸悪の根源は!!である。

 シェルには「ヒストリ展開」という機能がある。過去に入力したコマンドの履歴を出力する。

$ history
1  echo A
2  echo B
3  echo C
...
26 echo Z

 !に加えて行頭にある番号を指定すれば、その番号にあたるコマンドを実行できる。

$ !3
echo C
C

 出力結果は以下。

  • 1行目に実行するコマンド
  • 2行目に実行したコマンド結果

 番号でなく!を入力すると「直前のコマンド番号」を示すことになる。

$ !!
echo Z
Z

 番号でも!でもないとエラーになる。

$ !'
bash: !': event not found

 !の後ろをアルファベット1字にすると、履歴のうち先頭がそのアルファベットで始まるものを実行するらしい。

$ !a

 今回は以下のようなコマンド履歴が該当した。おそらく直近のaから始まるコマンドだと思われる。

apt search pygments | grep 'pygments' | grep 'インストール済み'

2. ''''と""の違い

クォート 呼び名 役割
'' シングル・クォート 展開せず文字列をそのまま出力する
'' ダブル・クォート 展開する

 文字列をそのまま出したいなら''(シングル・クォート)で囲む。

$ echo '!!'
!!
$ echo "!!"
echo "echo '!!'"
echo '!!'

 変数など特殊な文字を展開したいなら""(ダブル・クォート)で囲もう。

echo "~/"
echo "$HOME"
NAME=山田
echo "$NAME"
echo "$((1+1))"
echo "$(basename /tmp/a.txt)"
echo "!!"
echo "A \
B"

 上記を下記のようにシングル・クォートにしてみるとわかる。展開されずそのまま出る。

echo '~/'
echo '$HOME'
NAME=山田
echo '$NAME'
echo '$((1+1))'
echo '$(basename /tmp/a.txt)'
echo '!!'
echo 'A \
B'

見直してみる

 お分かりだろうか。そう、echo "Hello world!!"の末尾にある!!でヒストリ展開されていたのだ。つまり「直前のコマンド」を表示し、それを実行したのである。

1回目コマンド

$ echo 'Hello world!!'
Hello world!!

2回目コマンド

$ echo "Hello world!!"
echo "Hello worldecho 'Hello world!!'"
Hello worldecho 'Hello world!!'

 2回目コマンドの出力結果を見てみよう。!!echo 'Hello world!!'に置き換えるとわかる。!!の直前にスペースを多めに入れて表現すると以下。

echo "Hello world    echo 'Hello world!!'"
Hello world          echo 'Hello world!!'
  • 1行目は2回目コマンドecho "Hello world!!"を表示している
  • 2行目は2回目コマンドecho "Hello world!!"の出力結果を表示している
  • 1, 2行目の!!部分は1回目コマンド自体の文字列echo 'Hello world!!'になった

ググっても見つからない

  • bash "!!"
  • bash "!!" エクスクラメーション
  • bash 変数展開 "!!" エクスクラメーション
local A=dat
local dat=123
echo ${A} → dat
echo ${!A} → 123

使えぬエスケープ

 !を展開させずエスケープする方法

成否 コード 出力結果
echo '!' !
echo "\!" \!
echo "!" \!
  • C言語などでは信頼しきっていた\。まさかエスケープできないとは……。
  • PythonJavaScriptでは差などなかったシングルとダブルのクォート。まさか違いがあるとは……。

所感

 Hellow world!!ごときで躓く。これぞシェルクオリティ。一元様お断りな風格をはやくも醸し出す。孤高なギークを気取るにふさわしい言語。使いこなして「俺スゲー!」したくなること間違いなし。