xargsで雑なシェル芸

## xargsで雑に

https://adventar.org/calendars/4021

アドベントカレンダーの一日目だけど特にネタがない。
最近、開発用に雑にスクリプトを書く機会があり、その際にわりとxargsが役に立ったのでメモをする。
ファイル操作に関しては`find`コマンドの`-exec`、テキスト加工などは`awk`で済んでしまうことが多いが、
それぞれのツールを使いこなせなくても雑に書けるという点でxargsは優れていると思う。

### 便利オプション

自前のMacでしか使わないので、Linuxのxargsと体系が異なるかもしれない。
よく使うオプションだけ。

#### `-I`/`-J`
標準入力から受け取った文字列をその後の文字列に置換して展開する。
わりと一番使う気がする。

```
$ seq 5 | xargs -I_ echo seq=_
seq=1
seq=2
seq=3
seq=4
seq=5
```

こんな感じでawkとか使わなくても`echo`で雑に文字列を加工できる。

#### `-t`
実行前に、実行するコマンドを標準エラーに出す

```
$ seq 5  | xargs -t -I_ echo seq=_
echo seq=1
seq=1
echo seq=2
seq=2
echo seq=3
seq=3
echo seq=4
seq=4
echo seq=5
seq=5
```

実行するコマンドを確認するときに便利。

#### `-p`
実行するコマンドを1行1行確認する。

```
$ seq 5  | xargs -p -I_ touch _.txt
touch 1.txt?...y
touch 2.txt?...n
touch 3.txt?...y
touch 4.txt?...y
touch 5.txt?...y
$ ls
1.txt	3.txt	4.txt	5.txt
```

`y/n` で実行するか確認する。雑にツールを作るときとかに便利

#### `-o`
これはわりと最近追加されたやつっぽい。
標準入力を`/dev/tty`として新しく開ける

```
$ seq 10 | xargs -I_ touch _.txt
$ find . -type f | xargs rm -i
remove ./5.txt? remove ./4.txt? remove ./3.txt? remove ./2.txt? remove ./1.txt?
$ ls
1.txt	2.txt	3.txt	4.txt	5.txt
$ find . -type f | xargs -o rm -i
remove ./5.txt? y
remove ./4.txt? n
remove ./3.txt? y
remove ./2.txt? y
remove ./1.txt? y
$ ls
4.txt
```
上記は`-p`でも同じような事ができるが、`rm -i` のような標準入力を必要とするプロセスに対して標準入力を送る際に便利

#### `-P`
同時に実行する数を指定できる。デフォルトは1なので、並列数を増やせる。

```
$ time seq 5 | xargs -I_ sleep 1 && echo _
seq 5  0.00s user 0.00s system 63% cpu 0.004 total
xargs -I_ sleep 1  0.00s user 0.01s system 0% cpu 5.029 total

$ time seq 5 | xargs -P 5 -I_ sleep 1 && echo _
seq 5  0.00s user 0.00s system 69% cpu 0.003 total
xargs -P 5 -I_ sleep 1  0.00s user 0.01s system 0% cpu 1.006 total
```

### 使い方メモ

#### マージ済みのbranchを消すやつ

`-p` を使って都度聞くようにする

```
$ git branch --merged | grep -vE '^\*|master$|develop$' | xargs -p -I _ git branch -d _
git branch -d *** ?...y
git branch -d *** ?...y
git branch -d *** ?...y
```


#### 常時稼働のプロセスを同時に起動する

サーバや常時稼働のプロセスを同時に起動して同時に閉じたい際、
`&`でやるといちいちPIDを探してkillするのが面倒なので`-P` を使って同時に起動してまとめて落とせるようにする。

下は他のpodにポートフォワーディングした状態でサーバを起動するやつ。

今まではk9sで都度接続していたけど、マイクロサービスのコンポーネント分接続するのも面倒だった。
kubefwdだとportforwardするやつ全部に同じlabel付けないとnamespace内全部に繋いでしまうし、
kube-forwarderはプリセットみたいなのを作れないっぽいので、
結局Makefileに↓みたいなのを書いた。

```
$ echo \
  "kubectl port-forward svc/***-service 15001:15001" \
  "kubectl port-forward svc/***-service 15002:15001" \
  "go run server.go" \
  | rs 3 | xargs -P 3 -I _ sh -c _
  
Forwarding from 127.0.0.1:15001 -> 15001
Forwarding from [::1]:15001 -> 15001
Forwarding from 127.0.0.1:15002 -> 15001
Forwarding from [::1]:15002 -> 15001


   ____    __
  / __/___/ /  ___
 / _// __/ _ \/ _ \
/___/\__/_//_/\___/ v4.0.0
High performance, minimalist Go web framework
https://echo.labstack.com
____________________________________O/_______
                                    O\
⇨ http server started on [::]:8080
```

各コマンドが子プロセスとして動くので`Ctrl+C`でまとめて終了させれて便利。

```
$ ps
65027 ttys000    0:00.00 /bin/sh -c echo     \011"kubectl port-forward svc/***-service 15001:15001"     \011"kubectl port-forward svc/***-service
65028 ttys000    0:02.27 go run server.go
65031 ttys000    0:00.00 xargs -P 2 -I _ sh -c _
65032 ttys000    0:00.17 kubectl port-forward svc/***-service 15001:15001
65033 ttys000    0:00.17 kubectl port-forward svc/***-service 15002:15001
```

`webpack --watch` とAPIサーバを1つのプロンプトで同時に起動したいときとかもわりと使う。