はじめに
こんばんは!
MYJLab Advent Calendar 2023の11日目を担当するセイです!
今夜じゃねーよ!って思う人もいると思いますが、今書いてる現時点は夜だからこんばんはなんだよ。
最近は暇になってもおかしくは無い時期なんですが、いくらやってもタスクが終わらない毎日を送りつけています。
自分に「お前毎日毎日何やってこんなに忙しいんの」 問いかけ続けていますが、 何かはやっているはずです。
毎日行ったタスクを可視化できるツールがあったらいいなぁと思います。
などと、ダラダラ愚痴を吐いてきましたが、今回のテーマもこれと全く関係ないです。
今回のテーマ
突然ですが、皆さんCLI(コマンドラインインターフェイス、Command Line Interface)は使っていますか? Macって言うとターミナルですね。
CLIの操作を簡単にするために、たくさんのツールが 作られています。
CLIを使いこなせるとすごく便利でかっこいいですが、なかなか習得するのにコストがかかります。
自分も、このCLIツールの使い方、覚えたら絶対便利だけどめんどくさそうて言って逃げ続けていました。
awk
がまさにその一つでした。
でも、最近は宮治先生のTAがきっかけでawk
にハマったのも、自分の使用例をもとに 紹介しようと思います!
基礎編 ~今回出てくるコマンドも軽く紹介~
awk
今回のメインですね!
AWK(オーク)は、プログラミング言語の一つ。 テキストファイル、特に空白類(スペースの他、タブなど)やカンマなどで区切られたデータファイルの処理を念頭に置いた仕様となっています。(Wikipediaより)
そう、実を言うと、awkはプログラミング言語なんです。
特に、CLIでの入力に対して、こみいった処理ができます!
awkはパイプラインで渡された入力を処理して出力の使われ方が多いので、このセクションでは、macのホームディレクトリでls -l
コマンドの出力を渡して処理してみます。
パイプラインとか、標準入力とか、標準出力とかの名詞もちょろっと説明しようと思ったが、余裕がなかったので、分からない人は調べてね。
❯ ls -l
total 80
drwx------@ 6 chengchen staff 192 Nov 10 17:12 Applications
drwx------@ 4 chengchen staff 128 Nov 24 21:24 Desktop
drwx------+ 10 chengchen staff 320 Oct 8 14:39 Documents
drwx------@ 212 chengchen staff 6784 Dec 9 17:44 Downloads
drwx------@ 117 chengchen staff 3744 Dec 7 18:05 Library
drwx------+ 20 chengchen staff 640 Oct 8 15:54 Movies
drwx------+ 5 chengchen staff 160 Oct 24 2022 Music
drwx------+ 5 chengchen staff 160 Oct 24 2022 Pictures
drwxr-xr-x+ 4 chengchen staff 128 Feb 20 2022 Public
drwxr-xr-x+ 3 chengchen staff 96 Mar 11 2022 Sites
drwxr-xr-x@ 5 chengchen staff 160 Nov 24 12:38 important
drwxr-xr-x 3 chengchen staff 96 Nov 8 18:26 scikit_learn_data
-rw-r--r--@ 1 chengchen staff 39693 Dec 8 13:19 vs-picgo-data.json
Hello, world!
プログラミング言語なので、まずはHello, world!を出力してみましょう。
❯ ls -l | awk '{print "Hello, world!"}'
Hello, world!
Hello, world!
Hello, world!
Hello, world!
Hello, world!
Hello, world!
Hello, world!
Hello, world!
Hello, world!
Hello, world!
Hello, world!
Hello, world!
Hello, world!
Hello, world!
awkの基本的な構文は、{}
で囲まれた中に、条件 {処理}
という形で書きます。
渡すパラメータは、がならずシングルクォーテーション、''
で囲みましょう。理由は今回は割愛しますが、ダブルクォーテーションで囲むと挙動がおかしくなります。
構文として、print␣"文字列"
と書くと、文字列を出力します。
何回も出力されているのは、awk
はデフォルトで、入力を行ごとに処理して出力するからです。
NR
awkには、NR
という特殊変数があります。
NR
は、現在の行番号を表します。
たとえば、print␣NR,"Hello, world!"
の構文を使うと、行番号と一緒に出力できます。
print
は、,
で区切られた引数をスペースで区切って出力する。
❯ ls -l | awk '{print NR,"Hello, world!"}'
1 Hello, world!
2 Hello, world!
3 Hello, world!
4 Hello, world!
5 Hello, world!
6 Hello, world!
7 Hello, world!
8 Hello, world!
9 Hello, world!
10 Hello, world!
11 Hello, world!
12 Hello, world!
13 Hello, world!
14 Hello, world!
NF, $0, $1 ...
awkには、NF
という特殊変数があります。
NF
は、現在の行のフィールド数を表します。
フィールドというのは、行をスペースで区切ったもののことです。
例えば、以下の例だとtotal 80
の行はフィールド数が2で、
drwx------@ 6 chengchen staff 192 Nov 10 17:12 Applications
の行はフィールド数が9です。
❯ ls -l | awk '{print NF, $0}'
2 total 80
9 drwx------@ 6 chengchen staff 192 Nov 10 17:12 Applications
9 drwx------@ 4 chengchen staff 128 Nov 24 21:24 Desktop
9 drwx------+ 10 chengchen staff 320 Oct 8 14:39 Documents
9 drwx------@ 212 chengchen staff 6784 Dec 9 17:44 Downloads
9 drwx------@ 117 chengchen staff 3744 Dec 7 18:05 Library
9 drwx------+ 20 chengchen staff 640 Oct 8 15:54 Movies
9 drwx------+ 5 chengchen staff 160 Oct 24 2022 Music
9 drwx------+ 5 chengchen staff 160 Oct 24 2022 Pictures
9 drwxr-xr-x+ 4 chengchen staff 128 Feb 20 2022 Public
9 drwxr-xr-x+ 3 chengchen staff 96 Mar 11 2022 Sites
9 drwxr-xr-x@ 5 chengchen staff 160 Nov 24 12:38 important
9 drwxr-xr-x 3 chengchen staff 96 Nov 8 18:26 scikit_learn_data
9 -rw-r--r--@ 1 chengchen staff 39693 Dec 8 13:19 vs-picgo-data.json
上のコマンドには、$0
という特殊変数がありますが、
$0
は、現在の行全体を表します。
$1
は、現在の行の1番目のフィールドを表します。
$2
は、現在の行の2番目のフィールドを表します。
$3
は、現在の行の3番目のフィールドを表します。
...
例えば、
❯ ls -l | awk '{print $1}'
total
drwx------@
drwx------@
drwx------+
drwx------@
drwx------@
drwx------+
drwx------+
drwx------+
drwxr-xr-x+
drwxr-xr-x+
drwxr-xr-x@
drwxr-xr-x
-rw-r--r--@
❯ ls -l | awk '{print $6}'
Nov
Nov
Oct
Dec
Dec
Oct
Oct
Oct
Feb
Mar
Nov
Nov
Dec
1行の最後のフィールドを出力するには、$NF
と書きます。
❯ ls -l | awk '{print $NF}'
80
Applications
Desktop
Documents
Downloads
Library
Movies
Music
Pictures
Public
Sites
important
scikit_learn_data
vs-picgo-data.json
後ろから2番目のフィールドを出力するには、$(NF-1)
と書きます。
❯ ls -l | awk '{print $(NF-1)}'
total
17:12
21:24
14:39
17:44
18:05
15:54
2022
2022
2022
2022
12:38
18:26
13:19
条件指定
ls -l
みたいのコマンドは、1行目のtotal 80
とかは邪魔だったりしますので、それを除外することができます。
❯ ls -l | awk 'NR>1{print NR,$0}'
2 drwx------@ 6 chengchen staff 192 Nov 10 17:12 Applications
3 drwx------@ 4 chengchen staff 128 Nov 24 21:24 Desktop
4 drwx------+ 10 chengchen staff 320 Oct 8 14:39 Documents
5 drwx------@ 212 chengchen staff 6784 Dec 9 17:44 Downloads
6 drwx------@ 117 chengchen staff 3744 Dec 7 18:05 Library
7 drwx------+ 20 chengchen staff 640 Oct 8 15:54 Movies
8 drwx------+ 5 chengchen staff 160 Oct 24 2022 Music
9 drwx------+ 5 chengchen staff 160 Oct 24 2022 Pictures
10 drwxr-xr-x+ 4 chengchen staff 128 Feb 20 2022 Public
11 drwxr-xr-x+ 3 chengchen staff 96 Mar 11 2022 Sites
12 drwxr-xr-x@ 5 chengchen staff 160 Nov 24 12:38 important
13 drwxr-xr-x 3 chengchen staff 96 Nov 8 18:26 scikit_learn_data
14 -rw-r--r--@ 1 chengchen staff 39693 Dec 8 13:19 vs-picgo-data.json
printf
awkには、printf
という関数があります。
他の言語のformatted print機能を同じだったりします。
たとえば、printf
を使って、ls -l
の出力を整形してみましょう。
❯ ls -l | awk 'NR>1{printf "%02d %s\n",NR,$0}'
02 drwx------@ 6 chengchen staff 192 Nov 10 17:12 Applications
03 drwx------@ 4 chengchen staff 128 Nov 24 21:24 Desktop
04 drwx------+ 10 chengchen staff 320 Oct 8 14:39 Documents
05 drwx------@ 212 chengchen staff 6784 Dec 9 17:44 Downloads
06 drwx------@ 117 chengchen staff 3744 Dec 7 18:05 Library
07 drwx------+ 20 chengchen staff 640 Oct 8 15:54 Movies
08 drwx------+ 5 chengchen staff 160 Oct 24 2022 Music
09 drwx------+ 5 chengchen staff 160 Oct 24 2022 Pictures
10 drwxr-xr-x+ 4 chengchen staff 128 Feb 20 2022 Public
11 drwxr-xr-x+ 3 chengchen staff 96 Mar 11 2022 Sites
12 drwxr-xr-x@ 5 chengchen staff 160 Nov 24 12:38 important
13 drwxr-xr-x 3 chengchen staff 96 Nov 8 18:26 scikit_learn_data
14 -rw-r--r--@ 1 chengchen staff 39693 Dec 8 13:19 vs-picgo-data.json
%02d␣%s\n
は、2桁の数字を0埋めして出力して、スペースを空けて、文字列を出力して、改行するという意味です。
printf
はデフォルトで最後に改行を入れないので、\n
を入れて改行しています。
awk
の最後に
awk
はちゃんとしてプログラミング言語なので、一般の言語ように実はif文for文や各種組み込み関数もありますので、いくらでもはまれます。
ps
ps
コマンドは、実行中のプロセスのリストを表示するLinuxコマンドです。(ChatGPTより)
例:
我が家のopenwrtルータにpsを実行すると以下のようになる。
150行ぐらい出てくるのでこれは一部です。
❯ ps
PID USER VSZ STAT COMMAND
2454 logd 992 S /sbin/logd -S 64
2508 root 1464 S /sbin/rpcd -s /var/run/ubus/ubus.sock -t 30
2581 root 3688 S /usr/sbin/snmpd -Lf /dev/null -f -r
3024 root 1376 S /sbin/netifd
3091 root 1100 S /usr/sbin/odhcpd
3286 root 4764 S /usr/sbin/uhttpd -f -h /www -r OpenWrt -x /cgi-bin -u /ubus -t 3600 -T 30 -k 20 -A 1 -n 50 -N 100 -R -p 0.0.0.0:5000 -p [::]:50
...
gerp
grep
コマンドは、ファイル内で特定の文字列を検索するためのコマンドです。(ChatGPTより)
例:
さっきのpsコマンドを出力に、"docker"の文字列を探したい時にps | grep docker
を使うとdockerを含む行しか出力されなくなる。
❯ ps | grep docker
6428 root 738m S /usr/bin/dockerd --config-file=/tmp/dockerd/daemon.json
6785 root 740m S containerd --config /var/run/docker/containerd/containerd.toml --log-level warn
14278 root 688m S /usr/bin/docker-proxy -proto tcp -host-ip 0.0.0.0 -host-port 23333 -container-ip 172.19.0.2 -container-port 80
14287 root 688m S /usr/bin/docker-proxy -proto tcp -host-ip :: -host-port 23333 -container-ip 172.19.0.2 -container-port 80
29210 root 1120 R grep docker
kill
killコマンドは、プロセスを終了させるためのコマンドです。特定のプロセスID(PID)を使ってプロセスを終了します。例えば、kill 1234はPIDが1234のプロセスを終了させます。(ChatGPTより)
厳密にいうと、プロセスにシグナル(指令)を送るコマンドです。PIDはpsコマンドとかで調べられます。
例:
上の例で、dockerdのプロセスを終了させたい時にkill 6428
を実行すると、dockerのプロセスが終了します。
(dockerのdaemonプロセスを終了する方法としてとても推奨はできませんが)
❯ kill 6428
(正しく終了した場合は何も出力されない)
-9
オプションをつけると、強制終了します。
❯ kill -9 6428
(正しく終了した場合は何も出力されない)
xargs
xargsコマンドは、標準入力からデータを読み取り、それを引数として他のコマンドに渡すために使用されます。これにより、一連のデータを一度に複数のコマンドに渡して処理することができます。例えば、echo "1 2 3" | xargs touch
は、ファイル1, 2, 3を作成します。(ChatGPTより)
例:
❯ ls -l
total 0 (ファイルがない)
❯ echo "1 2 3" | xargs touch
❯ ls -l
total 0
-rw-r--r-- 1 chengchen staff 0 Dec 12 13:35 1
-rw-r--r-- 1 chengchen staff 0 Dec 12 13:35 2
-rw-r--r-- 1 chengchen staff 0 Dec 12 13:35 3
応用編
ここでは、僕が実際にawkを使っている場面を紹介します。
応用1: awkを使って、特定のプロセスを終了させる
シチュエーション
僕は自分のopenwrtルータに、vscodeのremote-sshで接続して設定をいじったっりすることがあります。
vscodeのremote-sshで接続していると、vscode-serverというプロセスがルータ上で立ち上がります。
普通なら、remote-sshを終了すれば、vscode-serverも終了しますが、このルータの環境では、vscodeを終了してもvscode-serverが終了せずに、プロセスがずっと残り続けて、非常にcpuリソースを食いつぶします。
なので、vscodeプロセスを強制終了させたいです。
解決策
そのためのコマンドが
❯ ps | grep /root/.vscode | awk '{print $1}' | xargs kill -9
流れは、psでvscodeのプロセスを見つけて、PIDがけを抽出して、kill -9で強制終了させます。
まずはps | grep /root/.vscode
でvscodeのプロセスを見つけます。
❯ ps | grep /root/.vscode
21572 root 1120 S sh /root/.vscode-server/bin/af28b32d7e553898b2a91af498b1fb666fdebe0c/bin/code-server --start-server --host
21582 root 292m S /root/.vscode-server/bin/af28b32d7e553898b2a91af498b1fb666fdebe0c/node /root/.vscode-server/bin/af28b32d7e
22386 root 304m R /root/.vscode-server/bin/af28b32d7e553898b2a91af498b1fb666fdebe0c/node --dns-result-order=ipv4first /root/
22397 root 255m S /root/.vscode-server/bin/af28b32d7e553898b2a91af498b1fb666fdebe0c/node /root/.vscode-server/bin/af28b32d7e
22429 root 1120 S grep /root/.vscode
次にawk 'print $1'
で1番目でのフィールドのPIDを取り出す。
ps | grep /root/.vscode | awk '{print $1}'
21572
21582
22386
22397
22447
最後に、xargs
で得られたPIDを全部kill
に渡せて、vscodeのプロセスを終了させることができます。
応用2: awkを使って、ファイル名を一括で変更する
シチュエーション
僕はこんな感じに、毎月TASAの勤務表を作成する必要がある。
❯ tree
.
└── 2023.11
├── SA勤務報告書_インフラ_11月.xlsx
├── TA勤務報告書_インフラ_11月.xlsx
├── TA勤務報告書_情報科学総合演習B_11月.xlsx
└── TA勤務報告書_機械学習_11月.xlsx
2 directories, 4 files
これを毎月毎月ファイル名を変更するのはめんどくさいので、一括で変更したい。
解決策
そのためのコマンドが
ls | awk '{ print $0; sub("11", "12"); print $0}' | xargs -n2 mv
流れは、ls
でファイル名を取得して、awk
で元ファイル名と新しいファイル名をprintして、xargs
でmv
に渡して、ファイル名を変更します。
まずはls
でファイル名を取得します。
❯ ls
SA勤務報告書_インフラ_11月.xlsx TA勤務報告書_情報科学総合演習B_11月.xlsx
TA勤務報告書_インフラ_11月.xlsx TA勤務報告書_機械学習_11月.xlsx
次に、awk '{ print $0; sub("11", "12"); print $0}'
で元ファイル名と新しいファイル名をprintします。
❯ ls | awk '{ print $0; sub("11", "12"); print $0}'
SA勤務報告書_インフラ_11月.xlsx
SA勤務報告書_インフラ_12月.xlsx
TA勤務報告書_インフラ_11月.xlsx
TA勤務報告書_インフラ_12月.xlsx
TA勤務報告書_情報科学総合演習B_11月.xlsx
TA勤務報告書_情報科学総合演習B_12月.xlsx
TA勤務報告書_機械学習_11月.xlsx
TA勤務報告書_機械学習_12月.xlsx
ここの文法を説明すると、複数のコマンドを実行する時は、;
で区切ります。最初にprint $0
でファイル名を出力して、sub("11", "12")
でファイル名($0
)の11を12に置換して、print $0
で新しいファイル名を出力します。
次は、xargs -n2 mv
で、mv
に渡して、ファイル名を変更しますが、-n2
のオプションを説明すると、xargs
はデフォルトで、渡された引数を1つずつ渡しますが、-n2
をつけると、2つずつ渡します。
つまり、こうなる
❯ ls | awk '{ print $0; sub("11", "12"); print $0}' | xargs -n2 echo
SA勤務報告書_インフラ_11月.xlsx SA勤務報告書_インフラ_12月.xlsx
TA勤務報告書_インフラ_11月.xlsx TA勤務報告書_インフラ_12月.xlsx
TA勤務報告書_情報科学総合演習B_11月.xlsx TA勤務報告書_情報科学総合演習B_12月.xlsx
TA勤務報告書_機械学習_11月.xlsx TA勤務報告書_機械学習_12月.xlsx
以上にように、受けたパラメータに渡しmv
で実行すると、ファイル名が変更されます。
最後に
awkは、CLIのツールの中でも、かなり強力なツールです。
こうやって少しつづ使い方を覚えていくと、つよつよエンジニアの道を歩めるかもしれません。
では皆だん、良いCLIライフを!