bashなどを使用する上で
引数と標準入力の使いわけ意識してますか?
echoやcatで渡しているのは、
引数?標準入力?
ここを理解して使っていくことで
幅が広がること間違いなしでしょう(受け売り)
【基礎】引数と標準入力
まずは単純なパターンから
catでファイル開く場合
引数で渡しても、標準入力で渡しても表示されます。
$ 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.
# 引数で渡すと成功
$ echo "aaa"
aaa
## 標準入力で渡すとエラー
$ echo < "aaa"
-bash: aaa: そのようなファイルやディレクトリはありません
catは引数があればファイルを引数として受け取り、引数がなければ標準入力を受け取る
catは、引数も標準入力も
ファイルを受け付けるコマンドです。
$man cat
...
DESCRIPTION
Concatenate FILE(s), or standard input, to standard output.
# ファイルの中身が連結され表示
$ cat a.txt b.txt
aaaaa
bbbbbb
# 標準入力で渡すと、複数を渡すことができない
$ cat <a.txt <b.txt
bbbbbb
【問題】 ファイルの中身を連結して出力せよ
Q. 下記のファイルの中身を連結して出力せよ
$ ls a.txt b.txt
lsやcatを使用すれば実現できそうですね
下記のような場合はどのような出力になるのでしょうか
$ ls | cat
a.txtとb.txtのそ各ファイルの中身がcatで連結されればいいなぁ
と思いますが、答えは...
#ファイル名が出力されてしまいます。
$ ls | cat
a.txt
b.txt
詳しく見ていくと
- パイプは標準入力として、次のコマンドにわたす
- lsの結果は標準入力として、まとめてcatにわたす
- catにわたされるのは文字列
このため
ファイルは連結されず、lsの結果のみが表示されてしまいました。
覚えておきたい表現
ここでbashで使える表現について覚えておきましょう。
-(ハイフン) : どこで標準入力を渡すのかを指定
パイプなどで渡された標準入力を
どこで使用するかを、指定できるのが ー(ハイフン)です。
コマンドにより、対応しているものと対応していないものがあります。
例えば
下記では渡しているb.txtを
a.txtの前に読み込むのか後に読み込むのかを指定することで
catの結果が変わっています。
$ cat b.txt |cat - a.txt
bbbbbb
aaaaa
#先程の結果と逆に。
$ cat b.txt |cat a.txt -
aaaaa
bbbbbb
$() : コマンド置換
文字列で渡されたコマンドを
コマンドとして実行させたい場合はコマンド置換$()を使用します。
バッククォートでも可能です。
よく使うやつですね。可読性が下がるので$()の方がおすすめです。
$ echo "ファイルは、$(ls)です"
ファイルは、a.txt
b.txtです
$ echo "ファイルは、`ls`です"
ファイルは、a.txt
b.txtです
問題の回答
ここまでわかれば、
lsの結果をcatでくっつけることも可能です。
echo ls | $(cat -)
lsした結果を、標準入力としてcatにわたし、
それをコマンドとして実行すれば、
それぞれのファイルの中身を連結してくれるはず!
$ echo ls | $(cat -)
a.txt b.txt
....あらららーー?
またしても、lsの結果だけ表示されてますね。
この問題は、catにわたるのはまとまった標準入力であり
catは、複数のファイルを渡す場合は、引数として
わたさなければならない点にあります。
引数しか受け付けないコマンドに、標準入力をわたす
上記のcatのように、引数としてわたしたい場合
多くのパターンがありますがよく使うのは
- xargs
- sed
xargs : 一行ずつ読み込んで実行する
渡された標準入力やファイルに対して
一行ずつコマンドを実行するコマンドです。
オプションが多いのですが最低限覚えておきたいのは
-I :後ろの文字列に標準入力を入れる+標準入力を一行ずつ処理+1プロセスずつ実行
-a :ファイルを読み込む場合必要
-P :同時プロセス数の上限。デフォルトは1
-t :実行するコマンドを標準出力する
$ ls | xargs -IXXX sh -c 'cat XXX'
aaaaa
bbbbbb
lsで渡された結果を標準入力として1行ずつ読み込み
I以下のXXX部分で読み込み、
shで実行しています。
無事、望み通りのものが出てきましたね。
sed : 置換だけではなく、コマンド実行にも使える
よく文字置換として使用されるsedですが
eオプションを使うことで一行ずつ実行することができます。
$ls | sed 's/^/cat /e'
aaaaa
bbbbbb
#これでもいけます
$ls | sed 's/^/cat /' |bash
こちらも望み通りの結果が出てきています。
改行が気になる場合は、sedなどで消せば良いでしょう。
まとめ
- 引数や標準入力に対応しているコマンドかはmanを見れば大抵わかる
- どこで標準入力を渡すのかを指定する-(ハイフン)
- 文字列をコマンドとして実行させる$()(コマンド置換)
- 引数のみ受け付けるコマンドに、標準入力を渡して実行させるならxargsやsed
この辺りを使用することで、ログ解析からの動作が非常に簡単になります。
身につけておきましょう。
例
awkでログの特定の列だけを抜き出してコマンド実行なども
汎用性の高い組み合わせです。
ログのIPアドレスからsshで接続し、コマンドを実行。
$ 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
$ 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先にある必要がある
- 標準入力として渡すなら、コマンドファイルがローカルにある必要がある
などが変わるため、注意が必要です。
# 引数として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'
ここまでできるようになると
一気にできることの幅が広がる気がしますね。