引数と標準入力、曖昧にして使ってない？


bashなどを使用する上で
引数と標準入力の使いわけ意識してますか？

echoやcatで渡しているのは、
引数？標準入力？

ここを理解して使っていくことで
幅が広がること間違いなしでしょう（受け売り）


## 【基礎】引数と標準入力

まずは単純なパターンから
catでファイル開く場合
引数で渡しても、標準入力で渡しても表示されます。
```sh
$ ls
a.txt  b.txt
# 引数
$ cat a.txt
aaaaa
# 標準入力
$ cat < a.txt
aaaaa
```

## 引数と標準入力のどちらかしか受け付けないコマンド

### echoは引数しか受け付けない
よくある例として
echoは引数しか受け付けません。
```
$man echo
...
DESCRIPTION
       Echo the STRING(s) to standard output.
```

```sh
# 引数で渡すと成功
$ echo "aaa"
aaa
## 標準入力で渡すとエラー
$ echo < "aaa"
-bash: aaa: そのようなファイルやディレクトリはありません
```

### catは引数があればファイルを引数として受け取り、引数がなければ標準入力を受け取る

catは、引数も標準入力も
ファイルを受け付けるコマンドです。
```
$man cat
...
DESCRIPTION
       Concatenate FILE(s), or standard input, to standard output.
```

```sh
# ファイルの中身が連結され表示
$ cat a.txt b.txt
aaaaa
bbbbbb

# 標準入力で渡すと、複数を渡すことができない
$ cat <a.txt <b.txt
bbbbbb
```

## 【問題】　ファイルの中身を連結して出力せよ

>Q. 下記のファイルの中身を連結して出力せよ
>```sh
>$ ls
>a.txt  b.txt
>```

lsやcatを使用すれば実現できそうですね

下記のような場合はどのような出力になるのでしょうか
```sh
$ ls | cat 
```

a.txtとb.txtのそ各ファイルの中身がcatで連結されればいいなぁ
と思いますが、答えは...

```sh
#ファイル名が出力されてしまいます。
$ ls | cat 
a.txt
b.txt
```


詳しく見ていくと
- パイプは標準入力として、次のコマンドにわたす
- lsの結果は標準入力として、まとめてcatにわたす
- catにわたされるのは文字列

このため
ファイルは連結されず、lsの結果のみが表示されてしまいました。



## 覚えておきたい表現
ここでbashで使える表現について覚えておきましょう。

### -（ハイフン） : どこで標準入力を渡すのかを指定

パイプなどで渡された標準入力を
どこで使用するかを、指定できるのが ー（ハイフン）です。
コマンドにより、対応しているものと対応していないものがあります。

例えば
下記では渡しているb.txtを
a.txtの前に読み込むのか後に読み込むのかを指定することで
catの結果が変わっています。

```sh
$ cat b.txt |cat - a.txt
bbbbbb
aaaaa

#先程の結果と逆に。
$ cat b.txt |cat a.txt -
aaaaa
bbbbbb
```

### $() : コマンド置換

文字列で渡されたコマンドを
コマンドとして実行させたい場合はコマンド置換$()を使用します。

バッククォートでも可能です。
よく使うやつですね。可読性が下がるので$()の方がおすすめです。

```sh
$ echo "ファイルは、$(ls)です"
ファイルは、a.txt
b.txtです

$ echo "ファイルは、`ls`です"
ファイルは、a.txt
b.txtです
```

### 問題の回答

ここまでわかれば、
lsの結果をcatでくっつけることも可能です。

```sh
echo ls | $(cat -)
```
lsした結果を、標準入力としてcatにわたし、
それをコマンドとして実行すれば、
それぞれのファイルの中身を連結してくれるはず！

```sh
$ echo ls | $(cat -)
a.txt  b.txt
```

....あらららーー？
またしても、lsの結果だけ表示されてますね。

この問題は、catにわたるのはまとまった標準入力であり
catは、**複数のファイルを渡す場合は、引数として**
わたさなければならない点にあります。

## 引数しか受け付けないコマンドに、標準入力をわたす

上記のcatのように、引数としてわたしたい場合
多くのパターンがありますがよく使うのは
- xargs
- sed　　

### xargs : 一行ずつ読み込んで実行する
渡された標準入力やファイルに対して
一行ずつコマンドを実行するコマンドです。

オプションが多いのですが最低限覚えておきたいのは
>
>-I :後ろの文字列に標準入力を入れる+標準入力を一行ずつ処理＋1プロセスずつ実行
>-a :ファイルを読み込む場合必要
>-P :同時プロセス数の上限。デフォルトは１
>-t :実行するコマンドを標準出力する
>

```sh
$ ls | xargs -IXXX sh -c 'cat XXX'
aaaaa
bbbbbb
```

lsで渡された結果を標準入力として1行ずつ読み込み
I以下のXXX部分で読み込み、
shで実行しています。

無事、望み通りのものが出てきましたね。

### sed : 置換だけではなく、コマンド実行にも使える

よく文字置換として使用されるsedですが
eオプションを使うことで一行ずつ実行することができます。

```sh
$ls | sed 's/^/cat /e'
aaaaa
bbbbbb

#これでもいけます
$ls | sed 's/^/cat /' |bash
```

こちらも望み通りの結果が出てきています。
改行が気になる場合は、sedなどで消せば良いでしょう。

## まとめ

- 引数や標準入力に対応しているコマンドかはmanを見れば大抵わかる
- どこで標準入力を渡すのかを指定する-(ハイフン)
- 文字列をコマンドとして実行させる$()（コマンド置換）
- 引数のみ受け付けるコマンドに、標準入力を渡して実行させるならxargsやsed

この辺りを使用することで、ログ解析からの動作が非常に簡単になります。
身につけておきましょう。


## 例
awkでログの特定の列だけを抜き出してコマンド実行なども
汎用性の高い組み合わせです。

ログのIPアドレスからsshで接続し、コマンドを実行。
```sh
$ cat instance.txt
api001a.ad-service    i-XXXXXX      10.3.50.128
automation-api001a.service    i-YYYYYY      10.3.59.51  
automation-api002c.service    i-ZZZZZZ      10.3.122.207
```

```sh
$ awk '{print $3}' instance.txt| xargs -IXXX -P 10 sh -c 'ssh -o StrictHostKeyChecking=no XXX "pwd"'
/home/developer
/home/developer
/home/developer
```

これを利用して
実行したいコマンドファイルをあらかじめ作成し、ssh先で実行させたい場合も使えます。

この時、コマンドファイルを
- 引数として渡すなら、コマンドファイルがssh先にある必要がある
- 標準入力として渡すなら、コマンドファイルがローカルにある必要がある

などが変わるため、注意が必要です。

```sh
# 引数としてcommand.txtを読み込む。command.txtはssh先にある必要がある
awk '{print $3}' instance.txt| xargs -IXXX -P 10 sh -c 'ssh -o StrictHostKeyChecking=no XXX "sh command.txt" > output.log'

# 標準入力としてcommand.txtを読み込む。command.txtはローカルにある必要がある
awk '{print $3}' instance.txt| xargs -IXXX -P 10 sh -c 'ssh -o StrictHostKeyChecking=no XXX < command.txt > output.log'
```

ここまでできるようになると
一気にできることの幅が広がる気がしますね。