Bourne Shell系で使えるシェルスクリプトチートブック
このコードは非常によく使う。スクリプトとデータとの相対位置を固定できるので、ワーキングディレクトリがどこにあっても実行することができる。
BASEDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && cd .. && pwd )"
set -eu
bash -eu script.sh
-u オプションを使用する場合、未定義変数を正しく処理するため、${1:-}とするとよい。 $1が空なら空のままであるという意味。
パラメータ変数
書式 | 内容 |
---|---|
$0 | スクリプト名 |
$$ | プロセスID |
$# | パラメータ数 |
$1,$2… | パラメータ |
$* | $IFSで分割された全パラメータ |
$@ | スペースで区切られたパラメータ |
$? | 最後に実行したフォアグラウンドコマンドの終了ステータス |
$! | 最後に実行したバックグラウンドコマンドのプロセスID |
$- | 現在のbashのオプションフラグ |
変数展開
書式 | 内容 |
---|---|
${変数名:-文字列} | 変数が未定義または空の場合 文字列に置換 |
${変数名:=文字列} | 変数が未定義または空の場合 文字列に置換して変数に代入 |
${変数名:?} | 変数が未定義または空の場合 エラー |
${変数名:?エラーメッセージ} | 変数が未定義または空の場合 エラー |
${変数名:開始位置:[長さ]} | 変数から文字列切り出し |
${#変数名} | 変数の文字数 |
${変数名#パターン} | 変数の先頭がマッチした場合 最短マッチ部を削除 |
${変数名##パターン} | 変数の先頭がマッチした場合 最長マッチ部を削除 |
${変数名%パターン} | 変数の末尾がマッチした場合 最短マッチ部を削除 |
${変数名%%パターン} | 変数の末尾がマッチした場合 最長マッチ部を削除 |
パターンとして使える文字は、* 任意の文字, ? 任意の一文字, […] 括弧内のいずれか
#!/bin/bash
set -euo pipefail
shopt -s nullglob
1行目: シバン(shebang)
2行目:
e 実行したコマンドの終了ステータスが0以外だったら終了
u 変数未定義だったら終了
o pipefail パイプの最後の終了ステータスが0以外だったら終了
3行目: ファイル名のワイルドカード「*」を常に展開する
bash -eu << 'EOS'
echo 'Hello World!'
EOS
sudo経由
sudo bash -eu << 'EOS'
whoami
EOS
seqを使う
for i in $(seq 0 10); do
echo $i
done
bash
for i in {0..10}; do
echo $i
done
THELIST="hello1 hello2 hello3"
for i in $THELIST; do
echo "[$i]"
done
cat > thelist.txt << 'EOS'
hello1
hello2
hello3
EOS
for i in $(cat thelist.txt); do
echo "[$i]"
done
rm thelist.txt
case 値 in
パターン1 ) 処理1 ;;
パターン2 ) 処理2 ;;
パターン3 ) 処理3 ;;
…
パターンn ) 処理n ;;
esac
readと組み合わせた例
while true; do
read -n1 -p "処理を継続しますか [y/n]? " yn
case $yn in
[Yy]* ) echo; echo "継続"; break ;;
[Nn]* ) echo; echo "終了"; exit ;;
* ) echo; echo "yまたはnを入力してください" ;;
esac
done
if [ -n "$(uname | grep 'Darwin')" ]; then
echo "macOSです"
elif [ -n "$(uname | grep 'Linux')" ]; then
echo "Linuxです"
fi
if の後にある [ が test コマンド
書式 | 内容 |
---|---|
-f file | ファイルがある |
-n string | 文字列が空ではない |
-z string | 文字列が空である |
s1 = s2 | s1とs2が同じ文字列である |
詳しくは man test
引数でコマンドを実行し、未定義コマンドの場合USAGEを表示
#!/bin/bash
set -euo pipefail
action1() {
echo "ACTION1"
exit 0
}
action2() {
echo "ACTION2"
exit 0
}
usage() {
echo "USAGE: $(basename $0) [ action1 | action2 ]"
exit 1
}
case "${1:-}" in
"action1" ) action1 ;;
"action2" ) action2 ;;
* ) usage ;;
esac
${0%/*}
絶対パスでカレントディレクトリを得る
BASEDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
絶対パスで一階層上を得る
BASEDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && cd .. && pwd )"
絶対パスで一階層上を取得(OSXは使えない)
BASEDIR=$(readlink -f $(dirname "$0")/..)
絶対パスで一階層上を取得(Perlを使う)
BASEDIR=$( perl -MCwd -MFile::Basename -e 'print Cwd::abs_path(dirname($ARGV[0])."/../")' $0)
awkももっとも簡単な使用例。awkはオークと読むらしい
cat hoge.txt | awk '{print $1}'
cat /etc/group | awk -F: '{print $1}'
#!/bin/bash
set -eux
run_user() {
whoami
}
run() {
local func=$(declare -f run_user)
sudo -u nobody bash -euxc "$func; run_user"
}
run
source ./script.sh
または
. ./script.sh
変数 BASH_SOURCE には 読み込んだスクリプトのファイル名が入っている。
THECODE="echo 'Hello World'"
eval $THECODE
使用例
thescript > /dev/null 2>&1
書式 | 内容 |
---|---|
2>&1 | STDERRをSTDOUTで出力 |
2>/dev/null | STDERRを出力しない |
1>/dev/null | STDOUTを出力しない |
>/dev/null | 1>/dev/null と同じ |
例
#!/bin/bash
set -eu
exec 1> >( tee hello.txt )
exec 2>&1
date +'%Y-%m-%d %H:%M:%S'
echo "Hello World!"
複数のコマンドを一つのスクリプトに含める際に便利
do_[引数] の functionを実行する。コマンドはCOMMANDSに定義する。
#-- {"wrap":"cat > command.sh"}
#!/bin/bash
set -eu
do_cmd1() {
echo "command1"
echo "application: $BASEDIR"
}
do_cmd2() {
echo "command2"
echo "application: $BASEDIR"
}
run() {
# sudoして自分自身を再実行させたい場合は
# 以下のコメントを削除
# if [ "$(id -u)" != "0" ]; then exec sudo $0 $@ fi
for i in $COMMANDS; do
if [ "$i" == "${1:-}" ]; then
shift
do_$i $@
exit 0
fi
done
echo "USAGE: $( basename $0 ) COMMAND"
echo "COMMANDS:"
for i in $COMMANDS; do
echo " $i"
done
exit 1
}
BASEDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
COMMANDS="cmd1 cmd2"
run $@