ユーザ用ツール

サイト用ツール


shellscript

ShellScript

bash

シェルスクリプトは基本的にはbashをターゲットに作成する。

#!/bin/bash -eu

...

shebangは#!/bin/shにはしない

dockerで起動する場合、alpineにはbashが入ってないため、ubuntu-slimをベースイメージにする。

shの実行オプション

  • -e 実行時コマンドが失敗したら(ステータス0以外を返したら)以降の処理を打ち切る
  • -x 変数を展開した状態で実際に実行されるコマンドを標準エラー出力に書き出す
  • -u 未定義の変数があれば処理を打ち切る
  • -C noclobberオプションを有効にする(リダイレクトでファイルを上書きしようとするとエラーになる、追記はOK)

shebangに追加されることが多い

#!/bin/bash -uex
 
...

testコマンド

test -f ./hoge.txt && echo "hoge.txtあるよ"
 
if [ -f ./hoge.txt ]; then
  echo "hoge.txtあるよ"
fi
 
if [[ -f ./hoge.txt ]]; then
  echo "hoge.txtあるよ"
fi
  • -e ファイルが存在すればtrue
  • -f 通常ファイルならtrue
  • -d ディレクトリならtrue
  • -L シンボリックリンクならtrue
  • -r 読み込み可能ならtrue
  • -w 書き込み可能ならtrue
  • -x 実行可能ファイルならtrue 
  • -z 文字列長が0ならtrue
  • -n 文字列長が0でなければtrue
  • -t 指定した番号のファイルディスクリプタが開かれていればtrue

[[ ]]

testコマンドのbash拡張

単語分割やパス展開しないので、変数をクオートで囲む必要ない

$ HOGE="a b c"
$ [ $HOGE == "a b c" ] && echo "一致"
bash: [: too many arguments
$ [[ $HOGE == "a b c" ]] && echo "一致"
一致
 
$ HOGE=hoge.*
$ [ $HOGE == "hoge.*" ] && echo "一致" || echo "不一致"
不一致
$ [[ $HOGE == "hoge.*" ]] && echo "一致" || echo "不一致"
一致

read

標準入力から1行読み取る

read line
echo $line

プロンプトを出して入力を促す

echo -n "User Name: "
read username
echo $username

zshでは以下のようにかける

read "username?User Name: "
echo $username

入力値をマスクしたい場合はread -s(決定時の改行も出力されなくなるのでechoを挟むとよい)

read -s "password?Password: "
tty -s && echo
echo $password | shasum -a 256

標準入力が尽きるまで行を読み取る(Ctrl-DでEOFを送信)

while read line
do
  echo $line
done

ファイルから行を読み込みする

while read line
do
  echo $line
done < hoge.txt

ヒアドキュメントから行を読み込みする

while read line
do
  echo $line
done << EOS
1
2
3
EOS

trap

シグナルのハンドリング

trap "echo trap sigint" INT

while :; do sleep 1; done

SIGINTをトラップすると、Ctrl-cで終了できなくなるので注意。またSIGKILLはトラップできない。

EXITで終了時処理

基本はシグナルのハンドリングだが、EXITとERRという疑似シグナルが利用できる。例えばmktempで作成した一時ファイルをスクリプト終了時に削除するなどの処理をEXITと組合わせて書ける。

hoge=/tmp/hogehoge
mktemp $hoge


trap "rm $hoge" EXIT 

テクニック

スクリプトファイル名を取得

FILENAME=$(basename $0)
FILENAME=${0##*/}

スクリプトのディレクトリを取得

カレントディレクトリにかかわらず、スクリプトが存在するディレクトリを取得する

DIR=$(cd $(dirname $0); pwd)

コマンドの存在チェック

「コマンド hoge が PATH にあれば…」の条件分岐

if [ -x "$(which hoge)" ]; then
  echo "hogeあるよ"
fi

ランダム文字列を生成

0-9A-Za-zのランダムな文字列を作成する。mac用

$ cat /dev/random | base64 | fold -w 16 | egrep -v "[+/]" | head -n 1

$ head -c 100 /dev/random | base64 | tr -d "+/" | head -c 16

セキュアな必要がないならば

$ shasum <(ls -al) | cut -d " " -f 1 

変数参照

${HOGE:-wang}   # HOGEに値がある場合はその値が展開される。HOGEが空の場合は、wangが展開される
${HOGE:+wang}   # HOGEに値がある場合はwangが展開される。HOGEが空の場合は何もしない
${HOGE:=wang}   # HOGEに値がある場合はその値が展開される。HOGEが空の場合は、wangと展開し、さらにHOGEにwangを代入
${HOGE:?wang}   # HOGEに値がある場合はその値が展開される。HOGEが空の場合は、wangを標準エラー出力に書き出して exit 1

シェル変数の加工

指定位置で切り取り

x=abcdefg
echo ${x:1:4}      # => bcde

変数の値の先頭・末尾でマッチした部分を削除する

x=hogehogefugafuga

echo ${x#hoge}       # => hogefugafuga 前方一致削除

echo ${x%fuga}       # => hogehogefuga 後方一致削除

echo ${x#h*e}      # => hogefugafuga * はワイルドカード

echo ${x##h*e}     # => fugafuga ##はワイルドカードを前方最長一致にする

echo ${x%f*a}        # => hogehogefuga * はワイルドカード

echo ${x%%f*a}       # => hogehoge %%はワイルドカードを後方最長一致にする

文字列置換

echo ${var/hoge/HOGE}   # => HOGEhogefugafuga

echo ${var//hoge/HOGE}  # => HOGEHOGEfugafuga グローバルマッチ

使用例:まとめてリネーム、拡張子まとめて変更

$ ls 
a.txt b.txt c.txt

$ for i in `ls`; do mv $i ${i%.txt}.php; done

$ls 
a.php b.php c.php

パラメータ展開

HOGE="hoge.fuga.piyo"
echo ${HOGE#*.}          # 最短前方一致削除 => fuga.piyo
echo ${HOGE##*.}         # 最長前方一致削除 => piyo

echo ${HOGE%.*}          # 最短後方一致削除 => hoge.fuga
echo ${HOGE%%.*}         # 最長後方一致削除 => hoge

ファイル名から拡張子を削除したい場合は ${filename%*.}、拡張子だけ取り出したい場合は ${filename##*.}

何もしないコマンド

コロンは何もしないビルトインコマンド

: hogehoge      # 何も起こらない

変数HOGEが空だった場合、nyanを代入する

: ${HOGE:=nyan}

変数HOGEが空だった場合、Error HOGE is emptyと出力して終了

: ${HOGE:?"Error HOGE is empty"}

可読性を考えると普通にifで書いた方がいいかも…

if [ -z "${HOGE}" ]; then
  echo "Error HOGE is empty" >&2
fi

標準入力がパイプ/リダイレクトされているか調べる

test -p /dev/stdin

/dev/stdinが名前付きパイプである(=stdinがパイプで接続されている)

test -t 0

ファイルディスクリプタ0番がterminalに紐づいている(=パイプやリダイレクトによるデータの入力がされていない)

if [ -t 0 ]; then
   # パイプやリダイレクトされてない
   # ./this_script
elif [ -p /dev/stdin ]; then
   # シェルスクリプトが | で接続されている
   # cat hoge.txt | ./this_script
else 
   # 標準入力がリダイレクトされている
   # this_script < hoge.txt
fi

シェルスクリプトの引数

$1 $2 で引数を取得できる。引数の数は $# で取得できる。引数の数が2桁になる場合は ${10} のように {} で括る。

# arg1.sh
echo $#
echo $1
echo $2
$ ./arg1.sh a b c d
4
a
b

$@ または $* でシェルスクリプトの引数全体を取得できる。

# arg2.sh
echo $@
echo $*
$ ./arg2.sh 1 2 3
1 2 3
1 2 3

$@$* はダブルクオートで括った場合の動作が異なる。$* は引数が一つの値にまとめられてしまう。ほとんどの場合、$@ の挙動が求められるので、$@ を使っておけば間違いない

# arg3.sh
node -p -e 'process.argv.slice(1).join()' -- "$*"
node -p -e 'process.argv.slice(1).length' -- "$*"

node -p -e 'process.argv.slice(1).join()' -- "$@"
node -p -e 'process.argv.slice(1).length' -- "$@"
$ ./arg3.sh 1 2 3
1 2 3
1
1,2,3
3

$* をダブルクオートで括った場合、空白スペースで結合される。結合文字は環境変数IFSで変更できるが影響範囲が大きいのであまり変えない方が良いだろう。

# arg4.sh
echo "$*" 
export IFS=":"
echo "$*"
$ ./arg4.sh 1 2 3
1 2 3
1:2:3

これらの挙動はシェル関数の引数でも同様である。

ヒアドキュメント

変数に格納

val=$(cat << 'EOS'
hoge
fuga
piyo
EOS
)

標準入力に渡す

while read -r line
do
  echo $line
done << 'EOS'
hoge
fuga
piyo
EOS
)

一度変数を経由する。«<はヒアストリングと呼ばれるものでPOSIXには定義されておらずshでは使用できない。bashやzshで使用できる

val=$(cat << 'EOS'
hoge
fuga
piyo
EOS
)
 
while read -r line
do
  echo $line
done <<< $val

クオートの有無は変数展開するかしないか

val='test'
 
while read -r line
do
    echo $line
done << "EOF"
${val}
${val}
${val}
EOF

クオートありの場合は変数展開されずに以下のように出力される

${val}
${val}
${val}
shellscript.txt · 最終更新: 2023/11/17 01:50 by nullpon