概要
※この記事のワンライナーを"シェル芸"と呼ぶには、本物のシェル芸人さんたちに失礼かもしれませんが、この場では"シェル芸"という言葉を使わせていただきます(_ _)
この記事では、シェル芸がどんな場面で役に立ったか、具体的な事例をひたすら列挙します。
「こんなこともできるのか!」、「これをたった一行のコマンドで処理できるのか!」、「なんか便利そう!」という感じで、Linuxコマンドの便利さ、シェル芸の雰囲気を感じ取ってもらうのが目的。
なのでコマンドの解説はなしです。
課題→シェル芸による解決、という一問一答形式で列挙していきます。
この記事で紹介するようなワンライナーがその場でパッと思いつくレベルになると、色々便利です。
bashさえあれば、プログラムを書かなくてもその場でサラッと強力な処理ができる、というのが良い。
ほんと色々なことがちょこっとコマンドを打てば解決できるのでとても気持ちが良い。
(ワンライナーで面倒な処理を一瞬で片づけてたら、たまに職場で魔法使いと言われたりもします...笑)
※今はweb開発の仕事をしていますが、転生前は非IT系でした。非ITの頃からシェル芸にはお世話になっていました。プログラムとか関係ない仕事でも、役に立つ場面は多いです。
※シェル芸と言いつつ、ちょいちょい自前のシェルスクリプトを使ってたりもします。
※良い事例思いついたら随時追加していきます!
テキストデータ加工系
■こんな感じのテキストデータが欲しい(連番の数字を生成して"",で囲み、かつ3行ごとに空行を入れる)
"100",
"101",
"102",
"103",
"104",
"105",
"106",
"107",
"108",
・
・
・
"898",
"899",
"900",
→ほい!
seq 100 900 | awk '{{print "\""$0"\","}if(NR%3==0){print ""}}' > ~/tmp.txt
■エクセルからコピーしてきた以下のような数値データのテキストファイルを右寄せ10カラム区切りに整形したい
1^ 1
2^ 4
3^ 9
4^ 16
5^ 25
6^ 36
7^ 49
8^ 64
9^ 81
10^ 100
・
・
・
※「^ 」はTabを意味する
→ほい!
cat tmp.txt | tr '\t' ' ' | awk '{printf "%10s%10s\n", $1, $2}'
1 1
2 4
3 9
4 16
5 25
6 36
7 49
8 64
9 81
10 100
・
・
・
■以下のようなテキストを出力したい(10カラムごとに列数を表示するコメント行のようなもの)(2022年9月28日追記)
#+++++++10++++++++20++++++++30++++++++40++++++++50++++++++60++++++++70++++++++80
→ほい!
seq 8 | awk '{print "++++++++"$0"0"}' | tr -d '\n' | sed -e 's/+/#/'
# または↓
seq 10 10 80 | awk '{print "++++++++"$0}' | tr -d '\n' | sed -e 's/+/#/'
#+++++++10++++++++20++++++++30++++++++40++++++++50++++++++60++++++++70++++++++80
■下記のような都道府県一覧が記載されたテキストファイルを加工し、idなどを振って、javascriptの定数定義ファイルを出力したい(2022年10月4日追記)
北海道 青森県 岩手県 宮城県 秋田県 山形県 福島県
茨城県 栃木県 群馬県 埼玉県 千葉県 東京都 神奈川県
新潟県 富山県 石川県 福井県 山梨県 長野県
岐阜県 静岡県 愛知県 三重県
滋賀県 京都府 大阪府 兵庫県 奈良県 和歌山県
鳥取県 島根県 岡山県 広島県 山口県
徳島県 香川県 愛媛県 高知県
福岡県 佐賀県 長崎県 熊本県 大分県 宮崎県 鹿児島県 沖縄県
→ほい!
cat prefs.txt | tr ' ' '\n' | awk 'BEGIN{print "const _prefs = ["}{print " { id: \""NR"\", ""name: \""$0"\" },"}END{print "];"}' > consts.js
const _prefs = [
{ id: "1", name: "北海道" },
{ id: "2", name: "青森県" },
// ・
// ・
// ・
{ id: "47", name: "沖縄県" },
];
■下記サイトから都道府県の緯度経度のみを抜き出したい(2022年10月7日追記)
→ほい!
curl -s "https://www.benricho.org/chimei/latlng_data.html" | grep 北緯 -A2 | grep -vE '緯|経|--' | tr '>' '\n' | grep '</span' | sed -e 's/<\/span//'
43.06417,141.34694
40.82444,140.74
39.70361,141.1525
・
・
・
・
ファイル探索系
■カレントディレクトリ以下の全ての「.xyz」という拡張子のファイル群から「abc」または「def」という文字列を検索したい
→ほい!
find . -type f -name '*.xyz' | xargs -I{} grep -E 'abc|def' {}
# ヒット数を数える場合↓
find . -type f -name '*.xyz' | xargs -I{} grep -E 'abc|def' {} | wc -l
# ※ファイル名も出力したい場合は、最後のgrepにオプション-Hをつける
■上記でさらに、「abc」または「def」という文字列の上下5行以内に'ghi'という文字列を大文字小文字区別せずに検索したい
→ほい!
find . -type f -name '*.xyz' | xargs -I{} grep -E 'abc|def' -C5 {} | grep -i 'ghi'
■上記でさらに「lmn」という文字列を排除したい
→ほい!
find . -type f -name '*.xyz' | xargs -I{} grep -E 'abc|def' -C5 {} | grep -i 'ghi' | grep -v 'lmn'
■エクセルファイルを全文検索
これはワンライナーでできなくもないがシェルスクリプト化しました。
そのスクリプトを使うと以下のように全文検索可能。
→ほい!
# カレントディレクトリ内のエクセルファイルに対して全文検索を行う
excelgrep '検索文字列' *.xlsx
# カレントディレクトリ以下の全てのディレクトリ内のエクセルファイルに対して全文検索を行う
find . -type f -name '*.xlsx' | xargs -I{} excelgrep '検索文字列' {}
$ excelgrep 'test' *.xlsx
test1.xlsx: test1
test2.xlsx: test2
test3.xlsx: test3
■カレントディレクトリ内の全てのテキストファイル(*.txt)の行数を数えて、行数でソートをかける(2022年9月27日追記)
$ ls -F
dir1/ dir2/ test1.txt test2.txt test3.txt test4.txt test5.txt test6.txt
→ほい!
wc -l *.txt | sort
5 test5.txt
20 test4.txt
50 test2.txt
100 test1.txt
120 test3.txt
220 test6.txt
515 total
■カレントディレクトリ以下の全てのテキストファイル(*.txt)の行数を数えて、行数でソートをかける(2022年9月27日追記)
→ほい!
find . -type f -name '*.txt' | xargs -I{} wc -l {} | sort
1 ./dir1/dir1_test1.txt
5 ./test5.txt
15 ./dir2/dir2_test1.txt
15 ./dir2/dir2_test3.txt
20 ./test4.txt
50 ./test2.txt
100 ./test1.txt
120 ./test3.txt
220 ./test6.txt
1000 ./dir1/dir1_test2.txt
1500 ./dir2/dir2_test2.txt
※行数を数えるにはwc -l
がよく使われる。
wc→word count、-l→lineだと思えば、覚えやすい。(調べたわけではないので合ってるかわかりません。)
ファイル・ディレクトリ操作系
■test_001~test_100というディレクトリを作成し、それぞれにtest.shというファイルをコピーして配置したい
→ほい!
seq -w 001 100 | while read -r line; do mkdir test_${line}; cp test.sh ${line}; done
■上記ディレクトリ名を「test_xxx」から「test-xxx」に変更する
→ほい!
\ls | while read -r line; do mv ${line} ${line//_/-}; done
■(Macユーザ向け)カレントディレクトリ以下の.DS_Storeを全て削除したい(2022年9月15日追記)
→ほい!
find . -type f -name '*.DS_Store' | xargs -I{} rm {}
■以下のようなテキストファイルに列挙されているファイル名のファイルを作成したい(2022年9月20日追記)
ringo
gorira
rappa
・
・
・
→ほい!
cat filename.txt | while read -r line; do touch ${line}; done
# または↓
cat filename.txt | xargs -I{} touch {}
■上記がディレクトリの場合(2022年9月20日追記)
※touch
をmkdir
とするだけ
→ほい!
cat filename.txt | while read -r line; do mkdir ${line}; done
# または↓
cat filename.txt | xargs -I{} mkdir {}
■プロジェクト内で、ファイル名に(大文字小文字区別せず)"user"(例)を含むファイルの中から、"TODO"または"FIXME"という文字列をgrepする(2023年1月12日追記)
(Userに関わる機能についての作業が一通り終わった際、関連ファイルのみからTODOとFIXMEを抜き出したい時)
→ほい!
find . -type f -iname '*user*' | xargs -I{} grep -E 'TODO|FIXME' {} -H --color=auto -A1
ネットワーク系
■使用されているIPアドレスの一覧を表示したい
→ほい!
seq 1 254 | while read -r line; do ping -c1 -W1 192.168.10.${line}; done | grep 'ttl'
※実は上記はarp -a
で同じことができるが、一応残しておくことにする
■Organizationの週間ランキングに載っている会社名を全て抜き出す(2022年9月24日追記)
※試したときは84ページあったので、84ページ全てから会社名を抜き出しています。
→ほい!
seq 84 | while read line; do curl -s "https://qiita.com/organizations?page=${line}" | sed -e 's/\"organizations\":\[/\n\"organizations\":\[/' | grep 'organizations' | grep -v 'newestOrganizations' | sed -e 's/,/\n/g' | grep '\"name\":' | sed -e 's/{\"name\"://' -e 's/\"organizations\":\[//'; done
■下記のようなテキストファイルのURL一覧全てに接続テストを行い、ステータスコード一覧を吐き出したい(2022/09/23追記)
URL1
URL2
URL3
・
・
・
→ほい!
cat urls.txt | while read -r line; do echo -n ${line}"->"; curl -sI ${line} | grep 'HTTP' ; done > results.txt
$ cat results.txt
URL1->HTTP/1.1 200 OK
URL2->HTTP/1.1 200 OK
URL3->HTTP/1.1 200 OK
・
・
・
Git系
■ディレクトリ中のファイルの、最後のコミッターを一覧化したい
→ほい!
# ファイル拡張子を指定したい場合
find . -type f -name '*.拡張子' | while read -r line; do echo $line $(git log -1 $line | grep 'Auth'); done
# ファイル拡張子を指定しない場合
find . -type f | while read -r line; do echo $line $(git log -1 $line | grep 'Auth'); done
■ディレクトリ中のファイルの中で、特定の誰かが最後のコミッターになっているファイルを探したい
→ほい!
# ファイル拡張子を指定したい場合
find . -type f -name '*.拡張子' | while read -r line; do echo $line $(git log -1 $line | grep 'Auth'); done | grep -E '名前1|名前2'
# ファイル拡張子を指定しない場合
find . -type f | while read -r line; do echo $line $(git log -1 $line | grep 'Auth'); done | grep -E '名前1|名前2'
※この操作をvimの:terminal
で行っていれば、この実行結果からそのままgf
でファイルにジャンプできるので、だいぶ捗ります。(というかこれに限らずファイル検索系全般そう)
■「xyz」という拡張子かつパスに特定の文字列を含まないファイルについてgitのコミット履歴一覧を表示したい
→ほい!
find . -type f -name '*.xyz' | grep -vE '文字列1|文字列2' | while read -r line; do echo '-------------------------------------------'; echo ${line}; git log ${line}; done
その他
■「abc」という文字列を含むdockerコンテナを削除したい
→ほい!
docker ps -a | grep 'abc' | awk '{print $1}' | xargs -I{} docker rm {}
■「abc」という文字列を含むdockerイメージを削除したい
→ほい!
docker images | grep 'abc' | awk '{print $3}' | xargs -I{} docker rmi {}
■Dockerイメージ「python」のタグで、version3.10以上の一覧を表示する(2022年9月27日追記)
※自前のシェルスクリプトを使います
→ほい!
docker-tags python -n10 | grep -E '^3\.1[0-9]'
3.11.0rc2-buster:386, arm64, arm, amd64
3.11.0rc2-bullseye:s390x, ppc64le, 386, arm64, arm, arm, amd64
3.11.0rc2:amd64, amd64, s390x, ppc64le, 386, arm64, arm, arm, amd64
・
・
・
■上記でRC版(評価版)を除く(2022年9月27日追記)
→ほい!
docker-tags python -n10 | grep -ivE '\-rc|[0-9]rc|rc\-|rc[0-9]' | grep -E '^3\.1[0-9]'
3.10.7-buster:386, arm64, arm, amd64
3.10.7-bullseye:s390x, ppc64le, mips64le, 386, arm64, arm, arm, amd64
3.10.7:amd64, amd64, s390x, ppc64le, mips64le, 386, arm64, arm, arm
・
・
・
■上記でさらにbullseye(Debian系の現時点で一番新しいversion)に絞り込む(2022年9月27日追記)
→ほい!
docker-tags python -n10 | grep -ivE '\-rc|[0-9]rc|rc\-|rc[0-9]' | grep -E '^3\.1[0-9]' | grep 'bullseye'
3.10.7-bullseye:s390x, ppc64le, mips64le, 386, arm64, arm, arm, amd64
3.10-bullseye:s390x, ppc64le, mips64le, 386, arm64, arm, arm, amd64
3.10.7-slim-bullseye:s390x, ppc64le, mips64le, 386, arm64, arm, arm, amd64
・
・
・
■上記でさらにcpuアーキテクチャがamd64
とarm64
両方に対応しているものに絞り込む(2022年9月27日追記)
→ほい!
docker-tags python -n10 | grep -ivE '\-rc|[0-9]rc|rc\-|rc[0-9]' | grep -E '^3\.1[0-9]' | grep 'bullseye' | grep 'amd64' | grep 'arm64'
3.10.7-bullseye:s390x, ppc64le, mips64le, 386, arm64, arm, arm, amd64
3.10-bullseye:s390x, ppc64le, mips64le, 386, arm64, arm, arm, amd64
3.10.7-slim-bullseye:s390x, ppc64le, mips64le, 386, arm64, arm, arm, amd64
・
・
・
質問・リクエストなど
「こんなことをワンライナーでできないですか?」みたいな質問・リクエスト等あれば是非コメントください(^^)