Python
Bash
python3
Linuxコマンド

sedもawkも使えないけどpythonでパイプに参加テク

概要

日々bashやらzshをいじる私ですが、恥ずかしいことにそらでsedもawkを使うことができません.
もちろん正規表現もダメダメです.
何度勉強してもまるで頭に定着しないんですね...

ただ、Pythonは迷う事無くコードを書くことができます.

そういうsedとawkが覚えられないけどシェルを有効に使用したいPythonユーザはこのテクが役に立つと思います.

注意点としてsedやawkなどの特化したUnixコマンドを使ったほうがPythonを使うより断然短く書けます.
ただそれらを知らなくてもシェル上で複雑な処理をパっと繋げられる方法があるという事の紹介です.

コード例

基本

基本的には以下の様な形になります.

# 行単位で処理する場合
ls | python3 -c "import sys;[print(i.strip()) for i in sys.stdin]" 

# まとめて処理する場合
ls | python3 -c "import sys; print(sys.stdin.read())" 
  • python3 -c : Pythonコードを実行するためのコマンド(私はaliasで短くしています)
  • sys.stdin : 標準入力(パイプで渡された文字を受け取る)(行単位のイテレータの様に振る舞う)

※ 組み込み関数input()はEOFをハンドリングするのが難しいためsys.stdinを使用している.
※ -cオプションはPYTHONSTARTUPを読み込む事ができない(なんでだろ).

文字を取り除きたい

下記の出力から./を取り除きたい。
多分sedで手軽にできると思うのですが書き方すら思い出せないので、わかるPythonでどうにかします.

% grep "data" -rl .
./file1
./file4
./file5
./file6

以下の様にします。

% grep "data" -rl . | python3 -c 'import sys;print(sys.stdin.read().replace("./",""))'
file1
file4
file5
file6
  • replace : 文字の置き換え

sedだと38文字程度でかけますが、pythonだと84文字でした.
ただsedの使い方を調べずにその場でどうにかできるのはとても良い・・.

CSV状出力のデータを加工したい

ls -lの内容から容量とファイル名だけ欲しい.
多分awkが得意な処理なのですがここはPythonで.

% ls -lh
total 14760
-rw-r--r--  1 user  user   617B  7 25  2017 listdir.go
-rwxr-xr-x  1 user  user   7.2M  7 25  2017 server1*
-rw-r--r--  1 user  user   254B  7 25  2017 server1.go
drwxr-xr-x  4 user  user   136B  7 25  2017 tmp/

以下の様にします.

% ls -l | python3 -c "import sys;[print('{4} {8}'.format(*i.split())) for i in list(sys.stdin)[1:]]"
617B listdir.go
7.2M server1*
254B server1.go
136B tmp/
  • split() : 引数が無いとスペースとか改行とか良い感じに分解してくれる
  • list(sys.stdin)[1:] : 一行目を飛ばす
  • print('{4} {8}'.format(*i.split())) : 分解した文字から4,8番目を出力

awkだと28文字程度で書けますが、pythonだと98文字かかりました.
割りと微妙な所ですが、やはりネットで調べなくても書けるのが良いですね!!!.

プログラマブルなフィルターを書く

ファイル名が6文字以下の物だけを表示.
正規表現を覚えていれば簡単にできそうですが.

% ls
f4         fi5        file2      filee3     filefile1

後置if文で条件を追加して以下の様にします

% ls -1 | python3 -c "import sys;[print(i.strip()) for i in sys.stdin if len(i)<7]"
f4
fi5
file2
  • len(x)<7 : 改行コードが含まれるので文字数+1

もっと複雑な物も実現できます.

Pythonで加工したデータをももう一度パイプで後続に渡す

もちろんできます.

% ls -1 | % ls | python3 -c "import sys;[print(i.strip()) for i in sys.stdin]" 
file1
file2
file3
file4
file5
file6
file7
file8

上記の出力の行数をwcコマンドで数えます.

% ls | python3 -c "import sys;[print(i.strip()) for i in sys.stdin]" | wc -l     
       8

最後に

PYTHONSTARTUPをpython3 -cオプションでも読み込んでくれるようになればもっと便利になるんですけどね・・。
REPLは対応しているのにどうしてこれは対応していないんだろう.