1 ポイント 投稿者 GN⁺ 2 시간 전 | 1件のコメント | WhatsAppで共有
  • シェルの : は、何も動作せず引数だけを評価して捨てる ヌルコマンド(null command) であり、パラメータ展開と組み合わせると必須引数の検査やデフォルト値の設定を簡潔に処理できる
  • : "${1:?missing argument, aborting!}" は、$1 が存在しないか空なら診断メッセージを stderr に出力し、0以外のステータスで終了する
  • ${VAR:=default}: の引数として渡すと、展開は実行するが結果の文字列は捨てられるため、展開結果をコマンドとして実行せず デフォルト値だけを設定 できる
  • リダイレクトによるファイルの空化やアクセス可否の検査、trap の空コマンド、set -u 環境での変数チェック、空の条件分岐にも : を活用できる
  • : "${VAR:=default}" は変数名を1回だけ書けばよいのでタイプミスの可能性を減らし、while : では常に成功する条件となって 無限ループ を表現する

ヌルコマンドとパラメータ展開

  • : は、引数を評価したあと結果を捨てる ヌルコマンド の組み込みコマンドである
    • 1971年の Thompson shell にまでさかのぼり、当時は ラベル であり、Unix最初のコメント記号としても使われていた
  • 4行の条件文の代わりに、次の1行で必須引数を検査できる
: "${1:?missing argument, aborting!}"
echo "Hello $1!"
  • パラメータ展開 構文 ${name:?diagnostic} は、$name が未設定または空かどうかを検査する
    • 値がなければ diagnosticstderr に出力し、シェルを0以外のステータスで終了する
    • 値があれば $name と同じ結果を返す
  • ヌルコマンドなしで ${HELLO:=123} だけを実行すると、展開結果の 123 がコマンドとして扱われて command not found が発生する
    • : ${HELLO:=123} なら展開を評価して HELLO=123 を設定しつつ、その結果は捨てる
  • : "${DATA_DIR:=/var/data}"DATA_DIR="${DATA_DIR:-/var/data}" と違って変数名を1回しか書かないため、DATA_DRI のような タイプミスの可能性 を減らせる

: の活用方法

  • デフォルト値の設定

    • 展開結果を実行せずに捨てつつ、変数のデフォルト値を設定する
: "${DATA_DIR:=/var/data}"
: "${RETRIES:=3}"
  • ファイルの空化とアクセス検査

    • リダイレクトと組み合わせると、1つ以上のファイルを 空にできる
: > error.log
: > error.log > access.log
  • サブシェルとリダイレクトを使って、ファイルの読み取り・書き込み可否も検査できる
( : < dataset.json ) && echo YES
( : >> result.json ) && echo YES
  • 空コマンドが必要な場所

    • trap のように構文上コマンドが必要だが、実際には何もしてはいけないときに使える
trap : INT
sleep 60
  • 条件文で空にしておきたい分岐にも置ける
if some-command; then
    :
else
    echo "command failed"
fi
  • 複数変数のチェック

    • set -u が有効な状態で、複数の変数が設定されているかを一度に検査する
set -u
: "$DEPLOY_ENV" "$HOST"
  • 無限ループ

    • : は常に成功する条件なので、無限ループ を簡潔に書ける
while :; do
  echo "I love colons"
  sleep 1
done

1件のコメント

 
GN⁺ 2 시간 전
Lobste.rsの意見
  • プロンプトを次のように構成すると、行全体をコピー&ペーストしてもプロンプト部分は無視され、コマンドだけが実行される
    : hayalci@tassadar ~/src/dool ; jj rebase -s dxw -d mrn
    • Plan 9に着想を得て、シェルのautocdオプションと次の形式も使っている
      ~/directoryname; <command>
      シェルのコマンド行をコピーするたびに、コマンドを最初に実行したディレクトリへそのままcdし続けることになる
    • zshでは、見えない改行なし空白をペーストしたときに左側の内容を消すよう設定している
      nbsp=$'\u00A0'  
      PS1="...$nbsp"  
      bindkey -s $nbsp '^u'  
      
  • Makefileで見栄えよく出力するときに、コロンをよく使う
    out/%.o: src/%.c  
        @echo CC $@  
        @$(CC) -c -o $@ $<  
    
    このやり方だと、makeが複数のechoプロセスを同時に実行し、同じストリームに出力して互いに干渉することがある。次のように変えると、子プロセスではなく**makeプロセス自身が出力**する
    out/%.o: src/%.c  
        : CC $@  
        @$(CC) -c -o $@ $<  
    
  • シェルスクリプトでこの手法を見たことはあったが、man :whatis :では有用なドキュメントを見つけにくかった
    trueコマンドに似ているが、--help--versionなどに特に反応せず、文字数も少ないという違いがある
    • Bashの組み込みコマンドは、manの代わりにhelpで調べるべき
      $ help :  
      :: :  
          Null command.
      
          No effect; the command does nothing.
      
          Exit Status:  
          Always succeeds.  
      
    • Fishでは、:は文字どおり**trueの別名**である
  • コロンがなくてもパラメータ展開は起きるのに、なぜわざわざヌルコマンドが必要なのか気になる
    • パラメータ展開自体はそのまま起きるが、ヌルコマンドのように結果を消費する構文がないと、シェルは展開された文字列を実行するコマンドとして扱う。この内容はFAQにも追記された
      % ${HELLO:=123}  
      zsh: command not found: 123  
      
      % : ${HELLO:=123}  
      % echo $HELLO  
      123  
      
    • 補間された文字列の結果が、実行するコマンドとして解釈される
      #!/bin/sh  
      "${1:?missing argument, aborting!}"  
      
      $ sh foo.sh sh  
      sh-5.3$  
      
  • コマンドを入力していて、あとでまた実行したくなったときは、行頭にコロンを付けて履歴に保存する。あとで履歴からその行を見つけ、先頭の1文字だけ消せば実際に実行できる
    ただし、引数の評価で生じる副作用に使う発想はなかった
    • :の代わりに#を付けてコメントとして保存することもできる。あとで実行する内容なら、その中の式を事前評価しない利点がある
    • zshではMeta-qまたはAlt-qで現在の行を一時的に脇にどけ、別のコマンドを実行したあと、終われば元の行をコマンドラインバッファに戻せる
      https://nathangrigg.com/2014/04/zsh-push-line-or-edit/
  • macOSでまだBash 3を使っており、次のエラーが出る
    -bash: !}": event not found
    • "!"の使用を外せば動くのか気になる
  • Con-colon-sionの次のコードは、while true; do...よりも人を混乱させる可能性が高い
    while : colons are more than punctuation; do  
      echo "I love colons"  
      sleep 1  
    done  
    
    while true; dowhile :; doより3文字長いだけで十分短く、このコードは筆者が伝えたい要点に合った例には見えない
    • そのまま従えという例ではなく、言葉遊びとして入れたコードである。このコードを理解できるならヌルコマンドを知っているということで、コロンは実際に句読点以上の意味を持つ
    • 本来見せたかったのは次のような無限ループだが、格好よく見せようとして要点を外したようだ
      while :; do  
          something  
      done