シェル・ワンライナー 160 本ノック
Tags: Linux, Shell
まとめ
general
- ファイルの内容を調べる
file some_image.jpg
- 実行時間を計測する
time ワンライナー
- for 文
for(i=1;i<$1;i++)printf(" ")
seq
順番に数値を出力する。
# 昇順
seq 5
# 降順
seq 5 1
sed
- 入力データに置き換え処理を施したうえで再出力する
-n
オプションをつけると、処理対象となった行のみを出力する
置き換え(s///
のパターン)
- 一回だけ置き換える
echo あいうえおあいうえお | sed 's/あ/か/'
# かいうえおあいうえお
- 何回も置き換える(
g
)
echo あいうえおあいうえお | sed 's/あ/か/g'
# かいうえおかいうえお
- 複数の置き換え条件を指定するには
;
で区切る
echo あいうえおあいうえお | sed 's/あ/か/g;s/い/き/g'
# かきうえおかきうえお
- 検索対象の文字を使う(
&
)
echo クロロエチルエーテル | sed 's/エチル/&&/'
# クロロエチルエチルエーテル
- 後方参照(
\1
や\2
など)-E
は拡張正規表現を有効にする-r
でも同じ意味- 無駄にエスケープ文字を入れなくて済むようになる
- 基本正規表現だけ使えればいいなら不要
echo クロロエチルメチルエーテル | sed -E 's/(エチル)(メチル)/\2\1/g'
# 以下のようにもかける
echo クロロエチルメチルエーテル | sed -E 's/(エ..)(...)/\2\1/g'
検索(//p
のパターン)
sed -n '/正規表現/p'
# 期間抽出(正規表現1に一致する行から、正規表現2に一致する行までを出力する)
sed -n '/正規表現1/,/正規表現2/p'
grep
- デフォルトで正規表現が使える
- 入力は 1 行ごとでも、スペース区切りでも OK
# 0を含むもの
seq 100 | grep "0"
# 8で始まるもの
seq 100 | grep "^8"
# 8で終わるもの
seq 100 | grep "8$"
# 80台
seq 100 | grep "8."
# 1, 10, 100, ...
seq 100 | grep "^10*$"
# 偶数
seq 100 | grep "[02468]$"
# 奇数
seq 100 | grep "[^02468]$"
-o
- マッチした部分のみが出力される
- 複数行で出力される
echo 中村 山田 田代 上田 | grep -o '[^ ]田'
# 山田
# 上田
-A 10
,-B 10
,-C 10
- 後 10 行、前 10 行、前後 10 行を出力する
-l
オプション - 一致した部分ではなくファイル名を出力する-R
オプション - ディレクトリ内のファイルを再帰的に読み込むgrep some_pattern ./*
- 特定のディレクトリ内のファイル内容を検索
awk
- grep にプログラム機能を加えたもの
awk '/正規表現/'
の書き方にすれば正規表現が使える$0
は「すべての列(行全体)」を表す$1
は「1列目の文字列 or 数値」を表す- データの n 列目を「第 n フィールド」と呼ぶ
print
は自動的に間のスペースと行末の改行を入れてくれる。awk ではよく使う。- 構成要素
- Pattern
- 抽出条件のこと
- 正規表現 or 計算式
- e.g.
$1%2==0
,/[a..b]/
- Action
- 処理のこと
{}
で囲まれている- e.g.
{print(...)}
- デフォルトは print
- Rule
- パターンとアクションの組み合わせのこと
- パターンだけ、アクションだけでも実行可能
- Pattern
# 正規表現で抜き出す
seq 5 | awk '/[24]/'
# 計算で抜き出す
seq 5 | awk '$1%2==2'
# マッチした行に処理を加える(以下の2つは等価)
seq 5 | awk '$1%2==0{printf("%s 偶数\n", $1)}'
seq 5 | awk '$1%2==0{print($1,"偶数")}'
# 2つ以上のルールを使う
seq 5 | awk '$1%2==0{print($1,"偶数")}$1%2==1{print($1,"奇数")}'
# 三項演算子を使う
seq 5 | awk '{print($1%2==0 ? "奇数" : "偶数")}'
- 期間抽出
- 正規表現 1 に一致する行から、正規表現 2 に一致する行までを出力する
awk '/正規表現1/,/正規表現2/'
- 列の結合(と比較)
awk '$1$2=="abcd"'
#`$1" "$2`は$1と$2をスペースでつなげたものになる
awk '$1" "$2=="abcd"'
-F
オプション- 区切り文字を明示的に指定する
awk -F:
だとコロンが区切りになる- デフォルトはスペース
- 組み込み変数
NF
- 総フィールド数NR
- 行番号
Pattern, Action のどちらでも利用可能なもの
- 比較
$1<"20191001
- Regex
$2~/REGEX_PATTERN/
- or は
||
Pattern
- BEGIN パターンは最初の行の処理前に実行される
- 無名のパターンは全ての行で実行される
- END パターンは最終行の処理後に実行される
awk 'BEGIN{a=0}{a+=1}END{print a}'
Action
- 三項演算子で条件分岐
awk '{print ($1<12?"午前":"午後")}'
- 三項演算子で条件分岐(列を追加する)
awk '{tax=($1<"20191001")?1.08:1.1;print $0,tax}'
- 一つのアクション内で2つのコマンドを実行したいときは
;
で区切る。
awk '{printf("a");printf("b")}'
- 列の値で計算する
awk '{print int($3*$4)}'
xargs
- 入力を横に並べて、出力する
- デフォルトでは
echo
が指定されたものとみなされる
- デフォルトでは
- 入力を横に並べて、コマンドに 引数(入力ではない) として渡したうえ実行してもらう
- 本来の使い方
seq 4 | xargs mkdir # => `mkdir 1 2 3 4`になる
seq 4 | xargs rmdir # => `rmdir 1 2 3 4`になる
- 決まった数ずつコマンドに渡していく
seq 4 | xargs -n2 mv
# `mv 1 2`と`mv 3 4`になる
- 下記のように数個ずつ画面出力するのにも使える。
- この際空行は削除される
- セキュリティには注意すること
seq 10 | xargs -n5
- 入力を改変してコマンドに渡す(@マークは別の文字に変えても OK)
seq 4 | xargs -I@ mkdir dir_@
- 並列実行する
xargs -P4 SOME_COMMAND
bc
計算をするために使う。
echo '1+1' | bc
sort, uniq
- データの中に何がいくつあるのか数えるときは、「sort で並び替えて uniq で数える」のが鉄板
- ただし、
sort -u
で同じことを一発で実現できる
- ただし、
- なぜ sort が必要なのか?
- uniq に与えられるデータはソートされていることが前提のため(一つのことだけやる。プログラムを簡素にする。UNIX 的な思想。)
seq 5 | awk '{print($1%2==0 ? "奇数" : "偶数")}' | sort | uniq -c
# 2列目から2列目を使って辞書順でソート(10 then 1)
sort -k2,2
# 2列目から2列目を使って数値順でソート(1 then 10)
sort -k2,2n
パイプ
|
- 左の結果を右に渡す
- 異なるプロセス間でのデータの受け渡し方法(プロセス間通信)の一つ
標準入出力
- しっかりしたコマンドは
- 別のコマンドに渡すべきデータを標準出力から出す
- 別のコマンドに渡すべきでないデータを標準エラー出力から出す
- デフォルトでは
- 標準出力と標準エラー出力は端末画面につながっている
- 標準入力はキーボードにつながっている
- ファイル記述子(ファイルディスクリプタ)
0
- 標準入力1
- 標準出力2
- 標準エラー出力
標準出力のリダイレクト
test.sh > file.txt
test.sh 1> file.txt
標準エラー出力のリダイレクト
test.sh 2> file.txt
# stderrの出力先を、いまstdoutがつながっている先に振り向ける(マージされる)
test.sh 2>&1
# パイプを使った方法(stdoutのみならずstderrも渡される)
test.sh |& cat
標準入力のリダイレクト
cat < file.txt
cat 0< file.txt
# 番外編)catとpipeを組み合わせる方法
cat a.txt | wc -l
# 番外編)引数にファイル名を指定
wc -l a.txt
read
標準入力を読み込んで変数にセットする
echo 123 | read v ; echo "$v" #=> 123
変数
値のセット
- スペースを入れてはだめ(コマンドとして解釈されるから)
a=ほげほげ
値の利用
- コマンドが実行される前に変数が文字列に入れ替わる
$a
${a} # この書き方が必要になることもある。変数名のあとに文字を続けたい場合など。
文字列操作
${a//}
や${a::}
などは Parameter Expansion(変数展開)と呼ぶ
a=あいうえお
b=かきくけこ
# 結合
echo $a$b # あいうえおかきくけこ
# 追加(破壊的)
$a+=$b ; echo $a # あいうえおかきくけこ
# 開始位置と長さ
echo ${a:1:3} # いうえ
# 置き換え
echo ${a/えお/けこ} # あいうけこ
計算
- 算術式展開
$(())
を使う- 括弧内で変数を使うとき、頭の
$
は不要 - 扱えるのは整数のみ
- 括弧内で変数を使うとき、頭の
a=6
b=2
echo $((a+b)) # 8
クォート
- シングルクォート
- 変数(
$1
など)をシェルで解釈せずに、そのままコマンドに渡す - 空白の入った引数をひとまとめにして引き渡す
- e.g. awk の
'{print 1+1}'
など
- e.g. awk の
- 変数(
- ダブルクオート
- 変数がシェルで解釈される
- シェルスクリプトでは変数はダブルクォートの中に配置するのが無難
- 変数の中身が空文字だと、なかったものとして扱われるため
re=""
grep "$re" /etc/passwd # ok
grep $re /etc/passwd # grep /etc/passwd と解釈されるため不正
配列、連想配列
配列
a=("$SHELL" "$LANG" "$USER")
# 取り出し(zshでは1スタート)
echo ${a[1]}
# 取り出し(全て)
echo ${a[*]}
echo ${a[@]}
# 要素数のカウントは#
echo ${#a[@]}
連想配列
declare -A b
b["SHELL"]="$SHELL"
b["LANG"]="$LANG"
b["USER"]="$USER"
# 取り出し
echo ${b["SHELL"]}
# 取り出し(全て)
echo ${b[*]}
echo ${b[@]}
# 取り出し(key, valueなど)
echo ${(k)b}
echo ${(v)b}
echo ${(kv)b}
# 要素数のカウントは#
echo ${#b[@]}
{}
は必須。- これがないと文字列の結合になってしまう
@
と*
はクォートした時の動作が異なる。*
をクオートした時には、IFS(デフォルトではスペース)で連結された文字列になる
位置パラメータ
- 主にシェルスクリプト内において、与えられた引数を取得する時に使われる
$1
1 番目の引数${22}
22 番目の引数 (二桁以上なら{}
が必須)$@
1 つ目以降の全ての引数$*
1 つ目以降の全ての引数 (ダブルクオートしたときに IFS で連結された文字列になる)
- 位置パラメータを手動でセットには
set
コマンドを使う
set aa bb cc
echo $2 #=> bb
for 文における位置パラメータの利用
for x in "$1" "$2" "$3" ; do echo $x ; done
# or
for x in "$@" ; do echo $x ; done
終了ステータス
- 以下で確認できる
$?
- 最後のコマンドの終了ステータス$pipestatus[@]
or$PIPESTATUS[@]
- パイプライン全ての終了ステータス
- ステータスの意味は各コマンドの
man
で調べられる - 128 以上の終了ステータスについては、128 を引くことで異常終了の原因となったシグナルを特定できる
ヒアストリング
パイプの別の書き方。以下の二つは等価。
echo $a | grep '[02468]$'
grep '[02468]$' <<< "$a"
while 文
- 終了ステータスが正常である限り実行が続く
- 以下の例では
read
が成功する限り
- 以下の例では
seq 3 | while read x ; do printf "%s " $x ; done
if 文
if 文の後に実行したコマンドの終了ステータスにより処理分岐される
a=5
if grep '[02468]$' <<< "$a" ; then
echo 偶数 ;
elif grep '[13579]$' <<< "$a" ; then
echo 奇数 ;
else
echo その他 ;
fi
テストコマンド
[
または test
で値の評価を行う。結果は終了ステータスとして得ることができる。
# 数値比較
a=0
[ "$a" -gt 10 ]
echo $? #=> 1
test "$a" -lt 10
echo $? #=> 0
# 文字列比較
b="hola"
[ "$a" = "hello" ]
# ファイル存在チェック
[ -e some.txt ]
# ファイル存在チェック (ディレクトリやリンクを除く)
[ -f some.txt ]
&&
と ||
# command1が成功したらcommand2も実行される
command1 && command2
# command1が成功したらcommand2は実行されない
command1 || command2
コマンド置き換え
$(コマンド)
- 該当部分がコマンドの実行結果に差し替えられる
a=きたうらわ
echo ${a}を逆さにすると$(echo $a | rev)
# きたうらわを逆さにするとわらうたき
$(<ファイル)
- ファイルの中身を引数に置き換える
- e.g.
echo "$(</etc/passwd)" > ~/passwd2.txt
プロセス置き換え
<(コマンド)
- コマンドの実行結果をファイル内容として、
<()
全体をファイル名として扱う
a=きたうらわ
cat <(echo $a) <(echo を逆さにすると) <(echo $a | rev)
# きたうらわを
# 逆さにすると
# わらうたき