Shell Script 入門
Basic
制御構文
- if
i=10
if [ $i -le 5 ]; then
i=`expr $i + 5`
elif [ $i -le 10 ]; then
i=`expr $i + 10`
else
i=`expr $i + 1`
fi
- for
max=10
for ((i=0; i < $max; i++)); do
echo $i
done
to_val=`expr 30 - 10 + 1`
for i in `yes "" | cat -n | head -30 | tail -$to_val`; do
:
done
- while
i=10
while [ $i -le 30 ]; do
i=`expr $i + 5`
done
ls file* |
while read f ; do
cp $f $f.backup
done
read varのようにすると入力を受付け、変数varに格納される。
- case
/etc/rc0.d/S01halt
case "$0" in
*halt)
message=$"Halting system..."
command="/sbin/halt"
;;
*reboot)
message=$"Please stand by while rebooting the system..."
command="/sbin/reboot"
kexec_command="/sbin/kexec"
;;
*)
echo $"$0: call me as 'halt' or 'reboot' please!"
exit 1
;;
esac
条件判定部分は正規表現が使える。
- until
i=10
until [ $i -le 5 ]; do
i=`expr $i - 1`
echo $i
done
特殊変数
$#
- コマンドラインの引数
$1
〜$9
,$0
- 引数それぞれ。 Positional Parameters という。
$*
$0
以外のコマンドライン引数
$@
$*
と類似。ただし$@
とした時、位置パラメータを評価せずにコマンドに渡すことが出来る
$?
- シェルが最後に実行したコマンドの終了状態を保持している。ほとんどのコマンドは成功時には0を返す
$$
- 現在のシェルのプロセス番号を保持している
$-
- シェルにセットされているオプションを保持している
$!
- バックグラウンドで実行された直前のプロセスのプロセス番号を保持しています
- Reference
- Title: Shell 特殊変数
- URL: https://qiita.com/a_yasui/items/ec4f75b300410af8958d
文字列
文字列操作
文字列の一部抜粋
- 左から :
cut -c -7
- 右から :
cut -c ``expr ${#var} + 1 - 7``-
- 左右から :
cut -c 9-13
- 左から :
大文字・小文字変換
tr "a-z" "A-Z"
正規表現でマッチした文字列の取り出し
- AWK : matchstr=
echo "STRING" | awk '{match($0, /PATTERN/); print substr($0, RSTART, RLENGTH)}'
- SED : matchstr=
echo "STRING" | sed 's/.*\(PATTERN\).*/\1/'
- GREP: matchstr=
echo "STRING" | grep -o 'PATTERN'
- AWK : matchstr=
特殊文字のトリミング
左側のトリミング後、右側のトリミング
タブやスペースを取り除きたい場合は、trimming_char=
printf " \t"
string="---abc-defgh----" trimming_chr="-" while [ "_$string" != "_${string#[$trimming_chr]}" ]; do string="${string#[$trimming_chr]}" done while [ "_$string" != "_${string#[$trimming_chr]}" ]; do string="${string%[$trimming_chr]}" done
ファイル名・ディレクトリ名の取得
- ファイル名取得
basename
filename="${filepath##*/}"
# 左側からの最大マッチング
- ディレクトリ名取得
dirname
dirpath="${filepath%/*}"
# 右からの最小マッチング
- ファイル名取得
文字列抽出
${var:-word}
:$var
が未定義か空文字の場合は文字列wordが読み出される${var-word}
:$var
が未定義の場合は文字列wordが読み出される${var:=word}
:$var
が未定義か空文字の場合は文字列wordが読み出され、かつ$var
にも代入${var=word}
:$var
が未定義の場合は文字列wordが読み出され、かつ$var
にも代入${var:?word}
:$var
が未定義か空文字の場合は文字列wordが読み出され、かつエラーの扱い${var?word}
:$var
が未定義の場合は文字列wordが読み出され、かつエラーの扱い${var:+word}
:$var
が未定義でも空文字でもなければ、文字列wordが読み出される${var+word}
:$var
が未定義でなければ、文字列wordが読み出される$#{var}
:$var
の文字数${var#PATTERN}
: 左端からPATTERNについて最小マッチングで切り落とされて読み出される${var##PATTERN}
: 左端からPATTERNについて最大マッチングで切り落とされて読み出される${var%PATTERN}
: 右端からPATTERNについて最小マッチングで切り落とされて読み出される${var%%PATTERN}
: 右端からPATTERNについて最大マッチングで切り落とされて読み出される${@:2}
: 二つ目以降の引数を取得
- Reference
- Title: Shell 特殊変数
- URL: https://qiita.com/a_yasui/items/ec4f75b300410af8958d
文字クラス
一部の環境では動作せず、使わないほうが無難
[[:alnum:]]
: 英数字。[0-9A-Za-z]
[[:alpha:]]
: 英字。[A-Za-z]
[[:digit:]]
: 数字。[0-9]
[[:lower:]]
: 英字の小文字[[:upper:]]
: 英字の大文字。[A-Z]
[[:blank:]]
: スペースとタブ[[:punct:]]
: 記号! " # $ % & ' ( ) * + , - . / : ; < = > ? @ [ \ ] ^ _ `` { | } ~
[[:xdigit:]]
: 16進数。[0-9A-Fa-f]
(参考) 正規表現
メタ文字セット
BRE(基本正規表現)
- 置換後文字列用
\n
: n番目の\(\)
で囲まれた範囲にマッチした文字列&
: マッチした文字列全体\x
: メタ文字&
またはsed等で正規表現の始まりを住めすために用いた文字自身を指定したい場合\\
:\
を指定する場合
(参照) どのUNIXコマンドでも使える正規表現
ERE(拡張正規表現)
- BREのときと比較して
\
をつけないなど、他多数
Others
ファイルの新規作成と追記
$ echo テスト > file
$ echo テスト >> file
ヒアドキュメント
$ cat << FIN > file
abc
def
FIN
ヒアストリング
$ A=テスト
$ sed 's/テ/ア/' <<< $A
# 以下の処理と同等
$ echo $A | sed 's/テ/ア/'
終了ステータス
$?
0で正常終了。正常ではないときは0以外の数字が終了ステータス。 テストコマンドでは正なら0、偽なら1、変な引数が指定されたら2のようにそれ以外の値が返される。
パイプで処理すると途中の処理のステータスが配列として格納される。bashで配列を扱うことができる機能
${PIPESTATUS[@]}
あるファイルがなければシェルスクリプトを終了するという処理の場合、以下のようにして書くことができる。
[ -f "/etc/passwd" ] || exit 1
引数
コマンド名 -- -引数
のように--
を指定することで、これ以降の-引数
はオプションではなく、引数として解釈する- 実行例
case "${arg}" in "--help") set -- "$@" "-h" ;; "--version") set -- "$@" "-V" ;; "--"*) echo "Failure: ${arg}" ;; *) set -- "$@" "${arg}" ;; esac
- 実行例
AWKとsed
AWK
- 関数
- printf
- sprintf
- sub
- gsub
- gensub
- length(t)
- split(s,a,fs)
- substr(s,p,n)
- index(s,t)
- match(s,r)
- tolower(s)
- toupper(s)
- 変数
- ARGC
- ARGV
- ENVIRON
- FILENAME
- FNR
- FS
- NR
- OFS
- ORS
- RS
$ seq 1 10 | xargs -n 5 > data
cat data
1 2 3 4 5
6 7 8 9 10
$ cat data | awk '{print $2,$4}'
2 4
7 9
$ cat data | awk '{a=3;print $(1+a)}'
4
9
NF
は各行の列数を表す予約語
$ cat data | awk '{print $(NF-1)}'
4
9
$ cat data | awk '$4>6'
6 7 8 9 10
$ cat data | awk '$5==5'
1 2 3 4 5
$ cat data | awk '$5=="5"'
1 2 3 4 5
$ echo {a..g} | xargs -n 1
a
b
c
d
e
f
g
$ echo {a..g} | xargs -n 1 | awk 'NR>=4{print $1,$1,$1}'
d d d
e e e
f f f
g g g
$ echo {a..g} | xargs -n 1 | awk 'NR>=4{print $1,$1,$1}NR<=4{print $1,$1}'
a a
b b
c c
d d d
d d
e e e
f f f
g g g
標準入力より前処理、後処理
$ seq 1 5 | awk 'BEGIN{a=100000}{a+=1}END{print a}'
100005
$ echo {1..5} | awk 'BEGIN{a=100000}{for(i=1;i<=NF;i++){a+=$i}}END{print a}'
100015
文字列を表示
$ seq 1 3 | awk '{printf("%d円\n",$1)}'
1円
2円
3円
フォーマットを指定して表示。標準出力はしないため、一旦変数に文字列を書き出して出力
$ echo {a..z} | awk '{$9=sprintf("%s%s",$9,$9);print}'
a b c d e f g h ii j k l m n o p q r s t u v w x y z
# Once per one line
$ echo abcdfabcde | awk '{sub(/cd/,"xy",$0);print}'
abxyfabcde
# Many times per one line
$ echo abcdfabcde | awk '{gsub(/cd/,"xy",$0);print}'
abxyfabxye
# Once per line, return converted strings
$ echo abcdfabcde | awk '{$0 = gensub(/cd/,"xy",$0);print}'
abxyfabcde
3行目のみ表示
$ echo {a..e} | xargs -n 1 | awk "NR==3{print \$0}"
c
3行目から4行目まで表示
$ echo {a..e} | xargs -n 1 | awk "NR==3,NR==4{print \$0}"
c
d
cの行からdの行まで表示
$ echo {a..e} | xargs -n 1 | awk "/c/,/d/{print \$0}"
c
d
CSVファイルの第3フィールドの合計値を求める場合
$ total_size=`awk -F "," "{T=T+\\$3} END {print T}" "$filename"`
-F,
のように指定すると文字列で区切る
sed
3行目のみ置換
$ echo {a..e} | xargs -n 1 | sed '3s/./???/'
a
b
???
d
e
2行目から最終行まで置換
$ echo {a..e} | xargs -n 1 | sed '2,$s/./???/'
a
???
???
???
???
bの行からdの行まで置換
$ echo {a..e} | xargs -n 1 | sed '/b/,/d/s/./???/'
a
???
???
???
e
指定した範囲(3行目から4行目を表示)。sedとしては入力された行をそのまま出力するのが基本動作なので-nを指定することで指定範囲の行が2行ずつ表示されることを抑えている
$ echo {a..e} | xargs -n 1 | sed -n '3,4p'
c
d
-nを指定しない場合
$ echo {a..e} | xargs -n 1 | sed '3,4p'
a
b
c
c
d
d
e
- xargsは標準入力から読み込んだ文字列をしていしたコマンドに引数として渡すコマンド
-I@
のようにオプションで文字を指定すると、別途その文字を指定した位置に引数として渡す-n 1
のようにいくつの引数を渡すかを指定-P 5
のように何並列でプロセスを立ち上げるかを指定。0
を指定するとできるだけプロセスを使うように指定
jq
jq Manual (development version)
-r
でダブルクオテーションを削除[]
や{}
で囲むと配列形式やオブジェクト形式で出力可能| @csv
のように渡すとCSV形式で出力可能| {InstanceId, Tags:(.Tags|from_entries)} | select(.Tags.Name | contains("test"))'
のようにすることでタグ指定で対象の項目を取得。Tags:
の部分は出力結果の表示名。from_entriesで"Key": "auto-stop"
、"Value": "yes"
のような形式を"auto-stop": "yes"
にまとめる| length
で長さ取得| unique[]
で配列に対して、重複排除。最後に[]
をつけない場合、配列で結果表示| fromjson
で文字列化されたJSONを復元jq '.events[] | .timestamp/1000 | todate'
のように秒単位にした上で| todate
を使用すると時間の表示形式を整形jq '.Functions[] | {FunctionName, VpcId:(.VpcConfig.VpcId//"NoVPC")}'
のように//
を指定すると左側がNULLのときは右側の要素に置き換える。jq -r '.Reservations[] | .Instances[] | [.InstanceId, (.Tags | if (.==null) then null else ([.[] | "\(.Key)=\(.Value)"] | join(",")) end)] | @csv'
のようにif () then ~ else ~ end
を仕様できる。また、join()で指定文字列で文字列の配列を1つの文字列に結合
$ aws ec2 describe-instances | jq '.Reservations[] | .Instances[] | .InstanceId'
"i-0a6b646d73a79370a"
"i-096d0b925a6d835d5"
"i-07ee081ad9075e2f7"
"i-045b56c08c46b11bd"
"i-043890efa180c22a2"
"i-0048d288f55da347d"
"i-0d79cc953246cb398"
"i-0e130f4914a5938a0"
"i-079f1057910324a39"
デバッグ
- デバッグ
- 未定義変数参照時にエラー
- スクリプトの冒頭に
#!/bin/sh -u
のように-u
オプションを付与 set -u
を宣言
- スクリプトの冒頭に
- 未定義変数参照時にエラー
- トレース
- スクリプトの冒頭に
#!/bin/sh -x
のように-x
オプションを付与
- スクリプトの冒頭に
- デバッグメッセージの分離
- FIFOパイプ
mkfifo /tmp/pipeしたらecho "test" > /tmp/pipe
のように書き出す- 別端末から
while [ 1 ];do cat /tmp/pipe;done
- FIFOパイプ
- パイプの中身
- 途中で
tee /dev/stderr
にリダイレクトすることやFIFOパイプを利用
- 途中で
Others
grepでタブ記号が入っている場合の検索
タブ記号をスペースに置き換える。[:space:]
は文字クラスを表し、タブや半角スペースなどの空白文字を指す
$ cat /etc/services | grep http | tr '\t' ' ' | grep ' 80/'
http 80/udp www www-http # World Wide Web HTTP
http 80/tcp www www-http # World Wide Web HTTP
$ cat /etc/services | grep http | grep '[[:space:]]80/'
http 80/udp www www-http # World Wide Web HTTP
http 80/tcp www www-http # World Wide Web HTTP
grepはデフォルトで基本正規表現。-Eオプションで拡張正規表現が利用でき、{5}
で5回繰り返すといった表現が可能になる。
- wcコマンド
- -m : ロケールを考慮した文字数カウント
- -l : 行数カウント
sed 's/./&\n/g'
で文字ごとに改行できるので、行数を数えることでも計算可能。
grep -o .
で任意の一文字を検索して一行ごとに出力。
$ echo 123456789 | sed 's/./&\n/g'
1
2
3
4
5
6
7
8
9
$ echo 123456789 | sed 's/./&\n/g' | wc -l
10
unique -c
: カウント
<()は
プロセス置換でbashの機能
$ diff <( cd /var/log/test1 ; find ) <( cd /var/log/test2 ; find )
HTMLの編集
$ cat test.html
<!DOCTYPE html>
<!-- saved from url=(0022)https://hayashier.com/ -->
<html><head><meta http-equiv="Content-Type" content="text/html; charset=windows-1252">
<!-- Global site tag (gtag.js) - Google Analytics -->
<script src="./test_files/osd.js"></script><script type="text/javascript" async="" src="./test_files/analytics.js"></script><script src="./test_files/f.txt" id="google_shimpl"></script><script async="" src="./test_files/js"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
:
コメントアウト削除
$ cat test.html | sed 's/<!--/\n&\n/g' | sed 's/-->/\n&\n/g' | sed '/<!--/,/-->/d'
<!DOCTYPE html>
<script src="./test_files/osd.js"></script><script type="text/javascript" async="" src="./test_files/analytics.js"></script><script src="./test_files/f.txt" id="google_shimpl"></script><script async="" src="./test_files/js"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
タグ削除。以下、実体参照が正しく使用されている場合
$ cat test.html | sed 's/<!--/\n&\n/g' | sed 's/-->/\n&\n/g' | sed '/<!--/,/-->/d' | sed 's;<[^<]*>;;g' | awk 'NF!=0'
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
ファイルの同期
$ rsync -av ~/ --exclude='.Trash' 192.168.5.1:~/home/hayashier/
sedでまとめて置換
sedで置換するルールを予めファイルに列挙しておいてまとめて置換することができる
$ cat /var/log/apach2/access.log* | ./month.sed | tail -n 1
#!/bin/sed -f
s/Jan/01/
s/Feb/02/
s/Mar/03/
s/Apr/04/
s/Mar/05/
s/Jun/06/
s/Jul/07/
s/Aug/08/
s/Sep/09/
s/Oct/10/
s/Nov/11/
s/Dec/12/
sedで部分一致箇所の抜粋
()
で囲っておくと、\1
のようにして取り出せる
$ echo 'A B C [D] "E" F G " "H" "I"' | sed 's/^\(.*\) \(.*\) \(.*\) \[\(.*\)\] "\(.*\)" \(.*\) \(.*\) "\(.*\)" "\(.*\)"$/\1|\2|\3|\4|\5|\6|\7|\8|\9/'
A|B|C|D|E|F G|"|H|I
Apacheのアクセスログの分析例
- 改行やスペースを除く、文字でないバイナリを除去する
- データ内の区切り文字以外の
"
や区切り文字を含んでいてっもフィールドを区切れるようにバックスラッシュは\\
と記録されているので、これを%5C
にエンコーディング "
は\"
と記録されているので、これを%22
にエンコーディング
#!/bin/bash -xv
B='\(.*\)'
D='"\(.*\)"'
P='\[\(.*\)\]'
STR='\1\x0\2\x0\3\x0\4\x0\5\x0\6\x0\7\x0\8\x0\9\x0'
sed 's;\\\\;%5C;g' < /dev/stdin |
sed 's;\\";%22;g' |
sed "s/^$B $B $B $P $D $B $B $D $D\$/$STR/" |
sed 's/_/\\_/g' |
sed 's/ /_/g' |
sed 's/\x0\x0/\x0_\x0/g' |
sed 's/\x0\x0/\x0_\x0/g' |
tr '\000' ' ' |
sed 's/ $//'
IPアドレスのソート
$ cat ip
192.168.1.2
10.245.0.1
10.95.20.1
40.1.212.4
203.113.10.1
$ sort -t . -k1,1n -k2,2n -k3,3n -k4,4n ip
10.95.20.1
10.245.0.1
40.1.212.4
192.168.1.2
203.113.10.1
以下の書籍等を参考にさせていただき、自分用の備忘録にまとめました。
- 覚えて便利 いますぐ使える! シェルスクリプトシンプルレシピ54
- コンパチブル・シェルスクリプティング(第5版)
- シェルプログラミング実用テクニック
- GihyoShellBookSamples
- Bash Reference Manual
- とほほのBash入門 !
Others
- setコマンドの使い方