シェル芸が役に立った実例集

## 概要
※この記事のワンライナーを"シェル芸"と呼ぶには、本物のシェル芸人さんたちに失礼かもしれませんが、この場では"シェル芸"という言葉を使わせていただきます(_ _)

この記事では、シェル芸がどんな場面で役に立ったか、具体的な事例をひたすら列挙します。
「こんなこともできるのか！」、「これをたった一行のコマンドで処理できるのか！」、「なんか便利そう！」という感じで、Linuxコマンドの便利さ、シェル芸の雰囲気を感じ取ってもらうのが目的。
なのでコマンドの解説はなしです。
課題→シェル芸による解決、という一問一答形式で列挙していきます。

この記事で紹介するようなワンライナーがその場でパッと思いつくレベルになると、色々便利です。
bashさえあれば、プログラムを書かなくてもその場でサラッと強力な処理ができる、というのが良い。
ほんと色々なことがちょこっとコマンドを打てば解決できるのでとても気持ちが良い。
(ワンライナーで面倒な処理を一瞬で片づけてたら、たまに職場で魔法使いと言われたりもします...笑)

※今はweb開発の仕事をしていますが、転生前は非IT系でした。非ITの頃からシェル芸にはお世話になっていました。プログラムとか関係ない仕事でも、役に立つ場面は多いです。
※シェル芸と言いつつ、ちょいちょい自前のシェルスクリプトを使ってたりもします。
※良い事例思いついたら随時追加していきます！

## テキストデータ加工系
##### ■こんな感じのテキストデータが欲しい(連番の数字を生成して"",で囲み、かつ3行ごとに空行を入れる)

```
"100",
"101",
"102",

"103",
"104",
"105",

"106",
"107",
"108",
  ・
  ・
  ・
"898",
"899",
"900",
```

→ほい！
```bash
seq 100 900 | awk '{{print "\""$0"\","}if(NR%3==0){print ""}}' > ~/tmp.txt
```

##### ■エクセルからコピーしてきた以下のような数値データのテキストファイルを右寄せ10カラム区切りに整形したい
```tmp.txt
1^ 1
2^ 4
3^ 9
4^ 16
5^ 25
6^ 36
7^ 49
8^ 64
9^ 81
10^ 100
  ・
  ・
  ・
```
※「^ 」はTabを意味する

→ほい！
```bash
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
```

→ほい！
```bash
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日追記)

```:prefs.txt
北海道 青森県 岩手県 宮城県 秋田県 山形県 福島県
茨城県 栃木県 群馬県 埼玉県 千葉県 東京都 神奈川県
新潟県 富山県 石川県 福井県 山梨県 長野県
岐阜県 静岡県 愛知県 三重県
滋賀県 京都府 大阪府 兵庫県 奈良県 和歌山県
鳥取県 島根県 岡山県 広島県 山口県
徳島県 香川県 愛媛県 高知県
福岡県 佐賀県 長崎県 熊本県 大分県 宮崎県 鹿児島県 沖縄県
```

→ほい！
```bash
cat prefs.txt | tr ' ' '\n' | awk 'BEGIN{print "const _prefs = ["}{print "  { id: \""NR"\", ""name: \""$0"\" },"}END{print "];"}' > consts.js
```

```javascript:consts.js
const _prefs = [
  { id: "1", name: "北海道" },
  { id: "2", name: "青森県" },
//        ・
//        ・
//        ・
  { id: "47", name: "沖縄県" },
];
```
##### ■下記サイトから都道府県の緯度経度のみを抜き出したい(2022年10月7日追記)
https://www.benricho.org/chimei/latlng_data.html

→ほい！
```bash
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」という文字列を検索したい

→ほい！
```bash
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'という文字列を大文字小文字区別せずに検索したい

→ほい！
```bash
find . -type f -name '*.xyz' | xargs -I{} grep -E 'abc|def' -C5 {} | grep -i 'ghi'
```

##### ■上記でさらに「lmn」という文字列を排除したい

→ほい！
```bash
find . -type f -name '*.xyz' | xargs -I{} grep -E 'abc|def' -C5 {} | grep -i 'ghi' | grep -v 'lmn'
```

##### ■エクセルファイルを全文検索
これはワンライナーでできなくもないが[シェルスクリプト化](https://qiita.com/UKIUKI_ENGINEER/items/76d1ba94c2e210bc5f5d)しました。
そのスクリプトを使うと以下のように全文検索可能。

→ほい！
```bash:書式
# カレントディレクトリ内のエクセルファイルに対して全文検索を行う
excelgrep '検索文字列' *.xlsx
# カレントディレクトリ以下の全てのディレクトリ内のエクセルファイルに対して全文検索を行う
find . -type f -name '*.xlsx' | xargs -I{} excelgrep '検索文字列' {}
```

```bash:使用例
$ 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
```

→ほい！
```bash
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日追記)

→ほい！
```bash
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というファイルをコピーして配置したい

→ほい！
```bash
seq -w 001 100 | while read -r line; do mkdir test_${line}; cp test.sh ${line}; done
```

##### ■上記ディレクトリ名を「test_xxx」から「test-xxx」に変更する

→ほい！
```bash
\ls | while read -r line; do mv ${line} ${line//_/-}; done
```

##### ■(Macユーザ向け)カレントディレクトリ以下の.DS_Storeを全て削除したい(2022年9月15日追記)

→ほい！
```bash
find . -type f -name '*.DS_Store' | xargs -I{} rm {}
```

##### ■以下のようなテキストファイルに列挙されているファイル名のファイルを作成したい(2022年9月20日追記)

```:filename.txt
ringo
gorira
rappa
  ・
  ・
  ・
```

→ほい！
```bash
cat filename.txt | while read -r line; do touch ${line}; done
# または↓
cat filename.txt | xargs -I{} touch {}
```

##### ■上記がディレクトリの場合(2022年9月20日追記)
※```touch```を```mkdir```とするだけ

→ほい！
```bash
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を抜き出したい時)

→ほい！
```bash
find . -type f -iname '*user*' | xargs -I{} grep -E 'TODO|FIXME' {} -H --color=auto -A1
```

## ネットワーク系
##### ■使用されているIPアドレスの一覧を表示したい

→ほい！
```bash
seq 1 254 | while read -r line; do ping -c1 -W1 192.168.10.${line}; done | grep 'ttl'
```

※実は上記は```arp -a```で同じことができるが、一応残しておくことにする

##### ■[Organizationの週間ランキング](https://qiita.com/organizations)に載っている会社名を全て抜き出す(2022年9月24日追記)
※試したときは84ページあったので、84ページ全てから会社名を抜き出しています。

→ほい！
```bash
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追記)

```:urls.txt
URL1
URL2
URL3
 ・
 ・
 ・
```

→ほい！
```bash
cat urls.txt | while read -r line; do echo -n ${line}"->"; curl -sI ${line} | grep 'HTTP' ; done > results.txt
```

```:results.txt
$ cat results.txt
URL1->HTTP/1.1 200 OK
URL2->HTTP/1.1 200 OK
URL3->HTTP/1.1 200 OK
 ・
 ・
 ・
```

## Git系
##### ■ディレクトリ中のファイルの、最後のコミッターを一覧化したい

→ほい！
```bash
# ファイル拡張子を指定したい場合
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
```

##### ■ディレクトリ中のファイルの中で、特定の誰かが最後のコミッターになっているファイルを探したい

→ほい！
```bash
# ファイル拡張子を指定したい場合
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のコミット履歴一覧を表示したい

→ほい！
```bash
find . -type f -name '*.xyz' | grep -vE '文字列1|文字列2' | while read -r line; do echo '-------------------------------------------'; echo ${line}; git log ${line}; done
```
## その他
##### ■「abc」という文字列を含むdockerコンテナを削除したい

→ほい！
```bash
docker ps -a | grep 'abc' | awk '{print $1}' | xargs -I{} docker rm {}
```

##### ■「abc」という文字列を含むdockerイメージを削除したい

→ほい！
```bash
docker images | grep 'abc' | awk '{print $3}' | xargs -I{} docker rmi {}
```

##### ■Dockerイメージ「python」のタグで、version3.10以上の一覧を表示する(2022年9月27日追記)
※[自前のシェルスクリプト](https://qiita.com/UKIUKI_ENGINEER/items/9fa237cd937c9d329595)を使います

→ほい！
```bash
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日追記)

→ほい！
```bash
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日追記)

→ほい！
```bash
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日追記)

→ほい！
```bash
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
      ・
      ・
      ・
```

## 質問・リクエストなど
「こんなことをワンライナーでできないですか？」みたいな質問・リクエスト等あれば是非コメントください(^^)