Skip to content

Instantly share code, notes, and snippets.

@tsukune-ch
Last active June 19, 2019 07:31
Show Gist options
  • Save tsukune-ch/3ad7e29b7adae2563d279b6e302f71d7 to your computer and use it in GitHub Desktop.
Save tsukune-ch/3ad7e29b7adae2563d279b6e302f71d7 to your computer and use it in GitHub Desktop.
シェルスクリプトのメモ

各オプションの意味

-e

通常、シェルスクリプトは一度実行されるとエラーの有無に関わらず最後まで処理を続行しようとする。

しかしこのオプションを指定することで、エラーが発生した時点でそれ以降の処理を中断するようになる。

-u

未割り当ての変数に対して読み込みを行おうとした際に、エラーを発生させる。

-x

実行したコマンドを全て標準エラー出力に出力する。

-C

リダイレクト演算子 > , >& , <> で既存のファイルを上書きしないようにする。

上書きをしたい場合は、リダイレクト演算子 > の代わりに >| を使用する。

数値比較の演算子

演算子 意味 覚え方
数値 1 -eq 数値 2 数値 1 と数値 2 が等しければ真 equal to
数値 1 -ne 数値 2 数値 1 と数値 2 が等しくなければ真 not equal to
数値 1 -lt 数値 2 数値 1 が数値 2 より小さければ真 less than
数値 1 -le 数値 2 数値 1 が数値 2 以下であれば真 less than or equal to
数値 1 -gt 数値 2 数値 1 が数値 2 より大きければ真 greater than
数値 1 -ge 数値 2 数値 1 が数値 2 以上であれば真 greater than or equal to

位置パラメータ

位置パラメータとは

シェルスクリプトを実行するときのコマンドラインに引数を渡すと、シェルはそれらの値を「位置パラメータ(Positional Parameters)」に設定します。

位置パラメータは、 $1$2 、…で個別の値を、 $*$@ ですべての値を展開できます。

位置パラメータの数は $# で展開できます。

$ cat test-dump.sh
##!/bin/sh
echo "$#"
echo "[$1] [$2] [$3]"
echo "[$*]"
echo "[$@]"
$ sh test-dump.sh foo bar qux
[foo] [bar] [qux]
[foo bar qux]
[foo bar qux]

シェルのマニュアルに依ってはシェルスクリプトの名前を示す変数 $0 も位置パラメータの一種のように記載しているものがありますが、この文書では含めないものとします。

位置パラメータに値を設定する

位置パラメータの値はシェルスクリプト(コマンド)の引数を受け取るだけでなく、スクリプト中で任意の値を設定することができます。

ただし、代入構文 変数名=値 は使用できません。

また、 $1 など個別の値だけ設定することもできません。

位置パラメータを設定するには、組込みコマンド set を利用して次のように記述します。

$ set -- 1st 2nd 3rd 4th
$ echo $#
4
$ echo "[$1] [$2] [$3]"
[1st] [2nd] [3rd]
$ echo "[$*]"
[1st 2nd 3rd 4th]
$ echo "[$@]"
[1st 2nd 3rd 4th]

set コマンドは位置パラメータ値だけでなく各種シェルオプションも受け付けるため、この例で set の最初の引数に指定しているオプション終端オプション -- が重要です。

次の例のように、 set はハイフン - で始まる引数をシェルオプションと解釈します。

set -a -b 実行後、現在のシェルオプション状態を示す $- の値に ab が追加され、位置パラメータには変化がないことに注目。

$ set aa bb
$ echo "[$*] (shell options=$-)"
[aa bb] (shell options=himBHPs)
$ set -a -b
[aa bb] (shell options=abhimBHPs)
$ set -- -a -b
$ echo "[$*] (shell options=$-)"
[-a -b] (shell options=abhimBHPs)

最初の位置パラメータの値が - で始まる文字列でなければ -- は不要ですが、値に応じて書き分ける手間や事故を防ぐため、常に -- を指定することを推奨します。

位置パラメータを破棄する

組み込みコマンド shift を実行すると、先頭の位置パラメータ値が破棄され、以降の値を先頭にシフトします。

$ set -- 1st 2nd 3rd 4th
$ echo "$# [$*]"
4 [1st 2nd 3rd 4th]
$ shift
$ echo "$# [$*]"
3 [2nd 3rd 4th]

位置パラメータをすべて破棄したいなら、 shift と位置パラメータ数 $# を組み合わせましょう。

$ set -- 1st 2nd 3rd 4th
$ echo $#
4
$ shift $#
$ echo $#
0

もしくは set を使用しましょう。

$ set -- 1st 2nd 3rd 4th
$ echo $#
4
$ set --
$ echo $#
0

ただし、 Solaris 10 の /bin/sh (POSIX sh 非互換)においては set -- は何の効果もなく、位置パラメータは変化しません。

POSIX sh かそれ以上の互換シェルなら問題ないと思われますが、先に紹介した shift $# を用いたほうが無難かもしれません。

位置パラメータ値を個別に展開する

先に紹介したように $1$2 、…を用います。

通常の変数展開と同じく、ダブルクオート " で括られていれば値が展開されるだけ、括られていなければ値の展開後にワード分割やパス名店会などが適用されます。

位置パラメータ値をすべて一括展開する

"$*" の場合(ダブルクオート内の $*

"$*" は、位置パラメータのすべての値の間にスペース が差し込まれた文字列に展開されます。

$ set -- 1st 2nd 3rd 4th
$ echo "$*"
1st 2nd 3rd 4th

値の間に差し込まれる文字は $IFS の先頭の文字(デフォルトはスペース )が採用されます。

$ set -- 1st 2nd 3rd 4th
$ (IFS='|'; echo "$*")
1st|2nd|3rd|4th

"$*" はダブルクオートに括られているため、展開後にワード分割はされません。

同様にパス名展開やプレース展開なども適用されません。

展開の結果は元のように個別の値にはならず、ひと塊の一つの値になります。

次のように "$*" の展開結果を位置パラメータに設定しなおしてみると確認できます。

$ set -- 1st 2nd 3rd 4th
$ echo "$# [$1]"
4 [1st]
$ set -- "$*"
$ echo "$# [$1]"
1 [1st 2nd 3rd 4th]

$*$@ の場合(ダブルクオートに括られていない $*$@

位置パラメータの各値がそれぞれ個別に展開された後、個別にワード分割やパス展開などが適用されます。

$*$@ どちらでも結果は変わりません。以下は $* だけ紹介します。

次の例の echo $* は、位置パラメータ展開の後、ワード分割により $IFS に含まれる文字(デフォルトはスペース、タブ、改行)で分割され、 echo コマンドの引数には 6 個の引数 foobarabcxyzXXXXXX が渡され、結果、それが出力されます(ワード分割で空白文字が失われ、パラメータの数が元の 4 個から 6 個に増えていることに注目。

$ set -- 'foo bar' 'abc ' 'xyz ' 'XXX   XXX'
$ echo $*
foo bar abc xyz XXX XXX

パラメータの数が 6 個になっていることを確認してみましょう。

$ set -- 'foo bar' 'abc ' 'xyz ' 'XXX   XXX'
$ echo $#
4
$ set -- $*
6

次の例の echo $* は、位置パラメータ展開の後、パス名展開の対象の文字 * を含むものがパス名展開され、 echo コマンドの引数には 2 個の引数 /bin/csh/bin/tcsh が渡され、結果、それが出力されています。

$ set -- '/bin/*csh'
$ echo $*
/bin/csh /bin/tcsh

"$@" の場合(ダブルクオートに括られている $@

位置パラメータの各値が展開されます。

それだけです。

パラメータの数は変化せず、ワード分割や各種展開もされません。

$ set -- ' foo  bar ' hoge '/bin/*csh'
$ echo "$@"
 foo  bar  hoge /bin/*csh

上記の実行例を一見すると、結果は "$*" の場合と変わりないように見えますが、次の例が示すように "$@" はもとの値と数を維持していることがわかります。

$ set -- ' foo  bar ' hoge '/bin/*csh'
$ echo $#
3
$ set -- "$@"
$ echo $#
3
$ echo "[$1] [$2] [$3]"
[ foo  bar ] [hoge] [/bin/*csh]

"$*" で同様のことを実行すると次のようになります。

$ set -- ' foo  bar ' hoge '/bin/*csh'
$ echo $#
3
$ set -- "$*"
$ echo $#
1
$ echo "[$1] [$2] [$3]"
[ foo  bar  hoge /bin/*csh] [] []

$*$@"$*""$@" の使い分け

$*$@ の使い所

もしあなたがシェルが行うワード分割処理や各種展開処理を理解していないなら、使わないでください。

危険です。

理解していても注意して使いましょう。

位置パラメータの値に空白文字( $IFS に含まれる文字)、パス名展開対象の文字( *? など)、そのほかの展開対象の文字(プレース展開など)が含まれていないことが確実であれば、安全です。

"$*" の使い所

位置パラメータの数に依らず、そのままの値をまとめて扱いたい場合に使用しましょう。

例えば、ログとして位置パラメータをすべてダンプしたい場合です。

log_error() {
  printf "%s: ERROR: %s\n" "$0" "$*" 1>&2
}

"$@" の使い所

位置パラメータの数と値を維持したまま扱いたい場合に使用しましょう。

例えば、ほかのコマンドの引数に与える場合です。

よくあるのはラッパースクリプトでの使用です。

#!/bin/sh
exec gcc -Werror "$@"

よく次のような例を見かけますが、いずれも誤動作の要因になります。

気をつけましょう。

駄目な例 1:

#!/bin/sh
## これは駄目な例です
## スクリプトの引数をワード分割、各種展開した結果がコマンドに渡ってしまいます
exec gcc -Werror $*

駄目な例 2:

#!/bin/sh
## これは駄目な例です
## スクリプトの引数をワード分割、各種展開した結果がコマンドに渡ってしまいます
exec gcc -Werror $@

zsh だと $*$@"$@" と同じ結果になるように見えるんだけど?

zsh のデフォルト状態では $*$@"$@" と等価です。

zsh オプション GLOB_SUBSTSH_WORD_SPLIT の解説を参照してください。

  • globsubst ( GLOB_SUBST )
    • クオートなしの変数展開時に展開後の値でパス名展開する
    • zsh のデフォルトはパス名展開しない
  • shwordsplit ( SH_WORD_SPLIT )
    • クオートなしの変数展開時に展開された値を空白文字で分割(ワード分割)する
    • zsh のデフォルトは空白文字分割しない

globsubstshwordsplit がよくわからない人のためのデモ:

$ zsh -c 'v="/*"; echo $v; set -o GLOB_SUBST; echo $v'
/*
/bin /boot /dev /etc /home /lib /lib32 /lib64 /lost+found /media /mnt /opt /proc /root /run /sbin /srv /sys /tmp /usr /var
$ zsh -c 'v=" foo "" bar "; echo $v; set -o SH_WORD_SPLIT; echo $v'
 foo  bar 
foo bar

root 権限で実行されているかをチェック

#!/usr/bin/env bash

set -e

if [[ $EUID -ne 0 ]]; then
  echo "This script must be run as root"
  exit 1
fi

テンポラリディレクトリを作成する

シェルスクリプトの終了時にディレクトリごと削除される。

#!/usr/bin/env bash

set -e

tmpdir=`mktemp -d`
trap "rm -rf $tmpdir" 0

echo dummy > $tmpdir/dummy.txt
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment