シェル・ワンライナー 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)
# きたうらわを
# 逆さにすると
# わらうたき
コマンドのグループ化(サブシェル)
()
で囲んだものごとに、別の bash を立ち上げて実行する
(command1 && echo "hello1") || (command2 && echo "hello2")
# 一時的にcdしたい時などにも便利
(cd /etc ; ls *.conf)
fork と exec
- fork
- プロセスの分身を作る
- 変数等は全て子にコピーされる
- e.g. サブシェルはこの仕組みで動く
- exec
- 自プロセスを別のプログラム(別のバイナリ実行用プロセス)に置き換える
- 例えば、シェルで
exec 化けたいコマンド
することで、同一 PID のままで、bash が sleep に化ける。 - ファイル記述子と環境変数は引き継がれる
- シェル変数は引き継がれない
- シェルがコマンドを呼び出すと実際に何が起こるか?
- シェルが
fork
され子シェルが生まれる - 子シェルが瞬時に
exec
してコマンドになる - この仕組みは
fork-exec
と呼ばれる
- シェルが
プロセス
- プロセス
- OS がコマンドやプログラムを管理する単位のこと
- 固有の番号が振られる(プロセス番号、プロセス ID、PID)
ps
- プロセス一覧pstree
- プロセス親子関係一覧
コマンドの種類
- 外部コマンド
- 実体のファイルがある
- ポータビリティが高い
- ビルトインコマンド
- ファイルがマシン上のどこにも存在しない
- シェルに直接プログラムされている
- シェルさえ動けば必ず使えるという利点がある
- 外部コマンドより高速
- プロセスを用意するという重い処理が不要になるため
/dev/null とは
- 入力された文字をそのまま捨てる特殊なファイル
- ビットバケツ、デブヌルなどと呼ばれる
- 用途
- 標準出力・標準エラー出力のいずれか又は両方を端末で見たくない場合
- ベンチマーク測定をするとき
ログ
1.2.a 端末を使う
- プロンプト -
$
マークのこと - コマンド -
echo $0
のようなもの - シェル - ソフトウェアのこと
- プロンプトを出したり、コマンドを受け取ったりしている
- bash はシェルの名前
- 端末 | terminal
- 遠くにあるコンピュータと接続して作業するのに使う機械のこと
- 元々は本当に機械だった
- データをコンピュータに送信し、返ってきたデータを印字出力するなど
- 今はエミュレータ
どこだったかな
|
- パイプ。左の結果を右に渡す。- 以下の通り、コマンド関連の用語はあいまいである
- コマンド
- 1 単語の場合はコマンドとして用いられるソフトウェアのこと
- e.g.
bc
、echo
- e.g.
- 2 単語以上の場合はシェルの受け付ける命令のこと
- e.g.
echo $0
- e.g.
- 1 単語の場合はコマンドとして用いられるソフトウェアのこと
- ワンライナー - コマンドを 2 つ以上組み合わせたもの
- パイプライン - パイプにコマンドがつながったもの
- コマンドライン - 打ち込んだ 1 行分の命令
- コマンド
1.2.b コマンドの止め方
Ctrl + C
- 強制終了。非常によく使う。Ctrl + D
- 端末にこれ以上ユーザが入力するものがないことを伝える。次点としておぼえておけば OK。
1.2.d ファイルへの保存
echo '1+1' | bc > result.txt
>
は、リダイレクト記号という
1.2.e ファイルとディレクトリの操作
echo あいうえお > somefile
mkdir tmp
mv somefile tmp/
# tmpフォルダの内容を一覧
ls -l tmp/
rm tmp/somefile
rmdir tmp/
1.2.f ファイルのパーミッション
chmod -r somefile
chmod +r somefile
- パーミッション
- ファイルの所有者
- ファイルの所有グループ
- それ以外
sudo
を毎回するのが面倒なときはsudo -s
1.2.g コマンドの調査
man は章立てになっている。たいていは 1 章に求めるものが書いてある。
man some_command
# 5章を見る
man 5 some_command
該当行とその次の1行を出力する
man ls | grep -A 1 '^ *-a'
1.3.g bash によるメタプログラミング
seq 4 | awk '{print "mkdir " ($1%2==0 ? "even_" : "odd_") $1 }' | bash
# 以下の4つのフォルダが作られる
# mkdir odd_1
# mkdir even_2
# mkdir odd_3
# mkdir even_4
- シェルスクリプトとは
- シェルにやってもらいたいことを順番に書いたファイル
bash ./somefile
で実行できる
- シェルスクリプトをコマンドとして使うには
- 1行目に shebang(
#!/bin/bash
)を入れる- OS が shebang に書いたコマンドを呼び出し、そのコマンドが 2 行目以降を読み込むよう手配される
- ファイルに
chmod +x
で実行権限を与える - これで
./somefile
で実行できるようになる
- 1行目に shebang(
Q1 ファイル名の検索
テキストファイルからの抽出の例:
# 王道
cat ./qdata/1/files.txt | grep '\.exe$'
cat ./qdata/1/files.txt | awk '/\.exe$/'
# - `-n`は各行を自動的に出力しない
# - `/正規表現/p`でマッチする行だけ出力する
cat ./qdata/1/files.txt | sed -n '/\.exe$/p'
Q2 画像ファイルの一括変換
あるフォルダ内にある png ファイルを全て jpg 形式に変換する方法
# 私の回答
ls *.png \
| sed -E 's/(.*)\.png$/\1/g' \
| awk '{print "convert " $1".png " $1".jpg"}'
| bash
# 鮮やかな回答
ls *.png | sed 's/\.png//' | xargs -I@ convert @.png @.jpg
# 発展型(時間計測と、並列実行による高速化)
time ls *.png | sed 's/\.png//' | xargs -P$(nproc) -I@ convert @.png @.jpg
Q3 ファイル名の一括変更
1
から10000
までのファイルがあるとして、0 埋めにファイル名を変更せよ
seq 1000 | xargs -P2 touch
ls | awk '{printf("%d %04d ",$1,$1)}' | xargs -n2 mv
Q4 特定の内容のファイルを探す
grep -l -R MY_FINDING_STRING | xargs rm
Q5 設定ファイルからの情報抽出
# 私の回答
cat ntp.conf | awk '/^pool/{print $2}'
# 模範解答
cat qdata/5/ntp.conf | awk '$1=="pool"{print $2}'
Q6 端末に模様を書く
# 回答例1
seq 5 | awk '{for(i=1;i<$1;i++)printf(" ");print "x"}' | sort
# 回答例2
seq 4 0 | awk '{for(i=$1;i>0;i--)printf(" ");printf("x\n")}'
Q7 消費税
20190901 ゼロカップ大関 10000
20190902 *キャベツ二郎 130
20191105 外食 13000
20191106 ストロングワン 13000
20191106 *ねるねるねるねる 30
20190912 外食 13000
cat qdata/7/kakeibo.txt | \
awk '{tax=($1<"20191001"||$2~/^あ/)?1.08:1.1;print $0,tax}' | \
awk '{print int($3*$4)}' |
awk '{sum+=$1}END{print sum}'
Q8 ログの集計
183.YY.129.XX - - [07/Nov/2017:22:37:38 +0900]
192.Y.220.XXX - - [08/Nov/2017:02:17:16 +0900]
66.YYY.79.XXX - - [07/Nov/2017:14:42:48 +0900]
::1 - - [07/Nov/2017:13:37:54 +0900]
133.YY.23.XX - - [07/Nov/2017:09:41:48 +0900]
cat qdata/8/access.log | \
awk -F: '{print $(NF-2)}' | \
awk '{print ($1<12?"午前":"午後")}' | \
sort | \
uniq -c
# awkのところの別解。こっちが直感的かも。
grep -o "....:.." | sed s/....://
Q9 期間抽出
# sed
qdata/9/log_range.log | sed -n '/24\/Dec\/2016 21:..:../,/25\/Dec\/2016 03:..:../p'
# awk
qdata/9/log_range.log | awk '/24\/Dec\/2016 21:..:../,/25\/Dec\/2016 03:..:../'
Q10 マークダウンの変換
cat qdata/10/headings.md | \
sed -E 's/^# +(.*)/\1\n===/g' | \
sed -E 's/^## +(.*)/\1\n---/g'
Q11 議事録の整理
cat qdata/11/gijiroku.txt | \
xargs -n2 | \
sed 's/すず/鈴木/g;s/さと/佐藤/g;s/やま/山田/g;s/ /: /g'
Q13 存在しなければファイル作成
[ -f some.txt ] || touch some.txt
Q14 羊を数える
# 共通部分
do
echo "羊が${n}匹"
n=$((n+1))
sleep 1
done
# while+変数
n=1
while [ $n -le 10 ]
# seq
seq 5 | while read n
# for + seq
for n in $(seq 100)
# シーケンス式
for n in {1..100}