シェルスクリプト

シェルスクリプトファイルの実体のあるディレクトリの絶対パスをシェルスクリプト自身が取得するには

-> シェルスクリプトファイルの実体のあるディレクトリの絶対パスをシェルスクリプト自身が取得するには

ファイルの相対パスから絶対パスに変換するには

-> シェルスクリプトファイルの実体のあるディレクトリの絶対パスをシェルスクリプト自身が取得するには

シェルスクリプトに与えられた任意の数の引数を繰り返し処理するには

以下のようにする。

for f in "$@"; do
    echo "$i"
done

以下は shift を使った例。

while [ -n "$1" ]; do
    echo "$1"
    shift
done

shift で、$2 にあったものが $1 に入るなど、引数が1つずつずれる。shift でも $0 は変わらない。

シェルスクリプトに与えられた引数をすべて特定のコマンドに渡すには

例えば ls に渡すには

ls "$@"

これでシェルスクリプトに a b "c d" と引数を渡すと ls "a" "b" "c d" が実行される。

ls "$*"

とすると、ls "a b c d" が実行されてしまう。

一定の確率でなにかをするには

シェルスクリプト実行時に、10分の1の確率でなにもしないようにするには、シェルスクリプトの先頭に以下のように書く。

if [ `expr $RANDOM % 10` -ne 0 ] ; then
    exit
fi

$RANDOM は参照するごとに乱数を返す変数のようなもの。expr で、10の剰余を計算している。

Ubuntu 12.04 でのデフォルトの /bin/sh が dash になっていて、$RANDOM が動かないので、これを使う場合はshebangを #!/bin/bash にする。

bash, dash の比較

dash は Ubuntu でのデフォルトの /bin/sh になっている(Ubuntu 12.04, 13.10 で確認)。

変数の内容を比較して分岐させるには

a=...
if [ "$a" = "abc" ]; then
    ...
fi

演算子

testコマンドの演算子

文字列1 = 文字列2
文字列が等しい場合
文字列1 != 文字列2
文字列が等しくない場合
-z 文字列
空文字列の場合
-n 文字列
長さ1以上の文字列の場合
数値1 -eq 数値2
数値が等しい場合
数値1 -ne 数値2
数値が等しくない場合
数値1 -lt 数値2
数値が < の関係
数値1 -le 数値2
数値が <= の関係
数値1 -ge 数値2
数値が >= の関係
数値1 -gt 数値2
数値が > の関係
-e ファイル名
ファイルまたはディレクトリが存在する場合。 Perlでも同じ。
-> リモートに特定のファイルが存在するかどうかを確認するには
-> ファイルまたはディレクトリの有無を取得するには
-f ファイル名
ファイルが通常のファイルとして存在する場合。 Perlでも同じ。
-> シンボリックリンクかどうかを取得するには
-d ファイル名
ファイルがディレクトリとして存在する場合。 Perlでも同じ。
-> シンボリックリンクかどうかを取得するには
-L ファイル名
ファイルがシンボリックリンクとして存在する場合。 Perlでは -l を使う。
-> シンボリックリンクかどうかを取得するには
-s ファイル名
ファイルサイズが0より大きい場合。 Perlでも同じ。
-> ファイルサイズが0かどうかを取得するには
-r ファイル名
パーミッションの設定によりファイルが読み取り可能な場合。
-w ファイル名
パーミッションの設定によりファイルが書き込み可能な場合。
-x ファイル名
パーミッションの設定によりファイルが実行可能な場合。 Perlでも同じ。
-t 0
標準入力がパイプでなく端末の場合。-t のあとの数字はファイル記述子。 (Perlでは -t STDIN と書く)
-t 1
標準出力がパイプでなく端末の場合。-t のあとの数字はファイル記述子。
ファイル名1 -nt ファイル名2
ファイル1のほうがタイムスタンプが新しい場合
ファイル名1 -ot ファイル名2
ファイル1のほうがタイムスタンプが古い場合

論理演算子

以下はtestコマンドの演算子。

!
否定
-o
論理和(OR)
-a
論理積(AND)

論理和・論理積は、シェルスクリプトの文法での &&, || も使える。

# 否定の例
if [ ! "$a" -eq "$b" ]; then
    ...
fi

# 論理和の例
if [ "$a" -eq "$b" -o "$c" -eq "$d" ]; then
    ...
fi

# testコマンドではなくシェルの || を使う論理和の例
if [ "$a" -eq "$b" ] || [ "$c" -eq "$d" ]; then
    ...
fi

否定の演算子はtestコマンドでなくても、任意のコマンドの前につけることもできる。

if ! grep "xxx" hoge.txt >/dev/null; then
    # hoge.txt に xxx が見つからなかった場合の処理
    ...
fi

特殊な変数

$1, $2, $3, …
シェルスクリプトに渡された引数。shift を呼び出すとずれていく。
$0
シェルスクリプトのファイル名
$*
シェルスクリプトに渡された引数。"$*" と書くと、引数が複数ある場合に、"param1 param2 param3" のように、全部をスペースでつなげた1つの文字列になる。 $0 は含まれない。
$@
シェルスクリプトに渡された引数。"$@" と書くと、引数が複数ある場合に、"param1" "param2" "param3" のようになる。 $0 は含まれない。
$#
シェルスクリプトに渡された引数の数。shift を呼び出すと1ずつ減っていく。
$?
最後に実行したコマンドの終了コード
$!
直前に起動したバックグラウンドプロセスのプロセスID。バックグラウンドプロセスは、最後に & を付けて起動させる。
$$
シェルスクリプト自身のプロセスID

永久ループを書くには

while : ; do date; sleep 1; done

: というのはなにもせずに常に終了コード0を返すコマンドで、これをwhile文の条件式のところに入れれば結果的に永久ループになる。

-> while文

複数のコマンドを並べる記号 ;, &, &&, ||

複数のコマンドを ;, &, &&, || で挟んで並べることで、いろいろな条件で複数のコマンドを実行させられる。

;
コマンドを順番に無条件に実行する。前のコマンドが終了してから次のコマンドを実行する。
&
前のコマンドをバックグラウンドジョブとして起動して、続けて次のコマンドを実行する。並列処理になる。
&&
前のコマンドが成功した場合に限り、次のコマンドを実行する。成功というのは、終了ステータスが0を返した場合。
||
前のコマンドが失敗した場合に限り、次のコマンドを実行する。失敗というのは、終了ステータスが0以外を返した場合。

エスケープ処理

-> 文字列のエスケープ処理

sudoでリダイレクトするには(root権限でしか書き込めないファイルにリダイレクトするには)

sudo hogehoge > /etc/foo/bar

とやっても /etc/foo/bar への一般ユーザでの書き込み権限がないと、 permission denied と言われてしまうので、以下のようにする。

sudo sh -c "hogehoge > /etc/foo/bar"

teeを使って以下のようにしてもよい。この場合、画面にも出力が表示される。 hogehoge自体にroot権限が不要な場合は、1つ目のsudoを外せる。

sudo hogehoge | sudo tee /etc/foo/bar

任意のファイルをシェルから作成するには

zsh では cat > foo.txt ように標準入力の内容をそのままファイルに書き出すのを単に > foo .txt と書くだけでできる。cat の3文字を省略できるだけだが。

-> catでのファイル作成とzshの機能

&&|| の優先順位

他のプログラミング言語での演算子優先順位との類推でなんとなく && のほうが || よりも優先度が高くなることを期待してしまいがちだが、同じ優先度のようだ。

従って、

cmd1 && cmd2 || cmd3 && cmd4

というコードは、コマンド4つとも終了コードが0の場合、 cmd1cmd2 だけが実行されるのではなく、

(cmd1 && cmd2 || cmd3) && cmd4

というようなイメージで、 cmd1cmd2cmd4 が実行される。

ディレクトリの中にあるファイルを1ファイルをのぞいてすべて羅列するワイルドカード

zshでは ^ というワイルドカードを使うとできる。

$ ls *
a.txt b.txt c.txt a.md b.md c.md
$ ls ^a.txt
b.txt c.txt a.md b.md c.md
$ ls ^*.txt
a.md b.md c.md

シェルスクリプトの中で実行するコマンドが異常終了したときにシェルスクリプトも終了させるには

シェルスクリプトの中で実行する一連のコマンドの中で、終了コードが0以外を返したコマンドがあった場合、その後続のコマンドを実行せずに、シェルスクリプト自体をその終了コードで終了させるには、シェルスクリプトの最初に set -e と書く。

set -e
echo "message 1"
test 1 = 2
echo "message 2"

これを実行すると、testコマンドで終了コード1を返すので、message 1 とだけ表示して、 message 2 とは表示せずに、シェルスクリプト自体が終了コード1を返す。

シェルスクリプトの中のすべてのコマンドではなく、特定のコマンドだけ異常終了を検知したければ、そのコマンドを実行した後の $? をチェックするか || で終了のコードを接続すればよい。

test 1 = 2
RET=$?
if [ $RET != 0 ]; then
    echo $RET # 異常検知時になんらかのどうさをさせたければここに
    exit $RET
fi

または

test 1 = 2 || { RET=$?; echo $RET; exit $RET; }

シェルスクリプトの中でテキストに色を付けて表示するには

# 赤字で Hello と表示
echo -e "\e[31mHello\e[0m"

echo コマンドはシェルの組み込みコマンドの場合があって、-e というオプションはシェルによってはサポートされていない。自分の Ubuntu 13.10 の環境では /bin/shdash で、この -e オプションが効かなかった。

/bin/echo -e ...

または

env echo -e ...

と書けば、シェルの組み込みコマンドではなく外部コマンドに強制できて、シェルに依存しなくなるので、回避できる。

\e[30m
\e[31m
\e[32m
\e[33m
\e[34m
\e[35m
マゼンタ
\e[36m
シアン
\e[37m
\e[4m
下線
\e[0m
色・書式をクリア

コマンドの実行時間を測定するには

timeコマンドを使うとよい。

標準エラー出力に色を付けて標準出力と区別しやすいようにするには

以下のようにするとよさそう。

(foobarcmd; 2>&1 >&3 | perl -npe 'BEGIN {$|=1} s/^(.*)$/\e[31m$1\e[m/' >&2) 3>&1

-> もう少し詳しい説明

リモートに特定のファイルが存在するかどうかを確認するには

-> リモートに特定のファイルが存在するかどうかを確認するには

シェルスクリプト中にコマンドが終了コード0以外を返したらそこで中断させるには

シェルスクリプトの中で set -e と書くか、shの起動オプションに -e を付ける。

#!/bin/sh

set -e

foo
bar
hogehoge

このスクリプトはだいたい以下と同じイメージ。

#!/bin/sh

foo || exit $?
bar || exit $?
hogehoge || exit $?

終了コード0以外のコードを返したコマンドがあったら、処理を続行せずに、シェルスクリプト自身もその終了コードを返して終了してくれる。

標準入出力が端末なのかパイプなのかリダイレクトなのかを判定するには

-> シェルスクリプトで標準入出力が端末なのかパイプなのかリダイレクトなのかを判定するには

標準出力の行頭にタイムスタンプを付けるには

FOOCOMMAND | perl -MTime::HiRes -npe 'BEGIN{$|=1} ($epoc,$micro)=Time::HiRes::gettimeofday();($s,$m,$h,$D,$M,$Y)=localtime($epoc);$_=sprintf("%04d/%02d/%02d-%02d:%02d:%02d.%03d ",$Y+1900,$M+1,$D,$h,$m,$s,$micro/1000).$_'

標準出力と標準エラー出力をまとめて1つのファイルに書き出すには

2>&1 とすれば標準エラー出力を標準出力に合流させられる。

$ COMMAND >foo.txt 2>&1

逆順に書くとダメ。これでは標準エラー出力だけ画面に出力される。

$ COMMAND 2>&1 >foo.txt

&> または >& という記号を使っても同じことができる。この2つは同じ。さっきの 2>&1 よりも短く簡潔に書ける。

$ COMMAND &> foo.txt

$ COMMAND >& foo.txt

追記したい場合は

$ COMMAND &>> foo.txt

で、これは以下と同じ。

$ COMMAND >>foo.txt 2>&1
このサイトは筆者(hydrocul)の個人メモの集合です。すべてのページは永遠に未完成です。
スポンサーリンク