Pythonワンライナーで解く『シェル・ワンライナー160本ノック』第2章(問題12から問題30まで)
はじめに
この記事では、書籍『シェル・ワンライナー160本ノック』の問題をPythonワンライナーで解いていきます。今回は第2章の問題を解きます。Pythonワンライナー以外に縛りはしないので、exec
やopy
も使わせていただこうと思います。何卒よろしくお願いいたします。
対象読者
シェル芸とPythonを愛する人々に読んでいただけますと幸いです。
注意
私のPythonワンライナーが正しい保証はありませんので、ご注意くださいませ。また、外部コマンドを使わずに解く問題でも、Pythonワンライナーを使いますので、ご了承くださいませ。
書籍情報
書籍『シェル・ワンライナー160本ノック』のページです。
書籍『シェル・ワンライナー160本ノック』のGitHubのページです。参考記事
Pythonワンライナーの記事です。参考にさせていただきました。ありがとうございます。
問題12
それではさっそくPythonワンライナーで問題を解いていきます。以下の内容のファイルをdouble.py
として保存します。処理A if 条件 else 処理B
というように書くと、条件がTrue
のときに処理Aが実行され、False
のときに処理Bが実行されます。
import sys;a=sys.argv;print(int(input())*2) if len(a) < 2 else print(int(a[1])*2)
以下のように実行すると、正しく処理されていることが分かります。
echo 3 | python3 double.py
# 6
echo 3 | python3 double.py 5
# 10
引数の取り方について、こちらの記事を参考にさせていただきました。ありがとうございます。
問題13
次は存在しないファイルの初期化問題です。if not os.path.isfile(a):
でunfile
が存在しているか確認して、存在していない場合はwith open(a,b) as c: c.close()
で空のファイルを作成しています。
python3 -c 'import os;a="unfile";b="w";exec("if not os.path.isfile(a):\n\twith open(a,b) as c: c.close()")'
ファイルの有無の確認について、こちらの記事を参考にさせていただきました。ありがとうございます。
ファイルの作成方法について、こちらの記事を参考にさせていただきました。ありがとうございます。問題14
次はループ問題です。問題自体は簡単なので、まだ使ったことがないlambda
を使ってみます。まずc=lambda b:[time.sleep(1),print("羊が"+str(b)+"匹")]
で「一秒ごとに羊がb匹と表示する」処理を作成します。そして最後に、[c(a) for a in range(1,101)]
で1から100までループさせます。
python3 -c 'import time;c=lambda b:[time.sleep(1),print("羊が"+str(b)+"匹")];[c(a) for a in range(1,101)]'
lambdaの使い方について、こちらの記事を参考にさせていただきました。ありがとうございます。
lambda内で複数の処理を実行する方法について、こちらの記事を参考にさせていただきました。ありがとうございます。問題15
次は大文字と小文字の変換問題です。Pythonではa.upper()
で大文字に変換できます。以下が小問1の解答です。
echo I am a perfect human | python3 -c 'a=input();print(a.upper())'
小問2では、フラグb
を用意して、b==1
ならば大文字、b==0
ならば小文字を出力します。また、b
の初期値はTrue
にして、大文字を出力するたびにFalse
にします。また、-
を出力するたびにTrue
にします。
echo pen-pineapple-apple-pen | python3 -c 'import re;a=input();b=1;c="-";e="";exec("for d in range(len(a)):\n\tif b:\n\t\tprint(a[d].upper(),end=e);b=0\n\telse:\n\t\tif a[d]==c:\n\t\t\tb=1;print(a[d],end=e)\n\t\telse:\n\t\t\tprint(a[d],end=e)");print("")'
文字列の大文字への変換について、こちらの記事を参考にさせていただきました。ありがとうございます。
問題16
次は変数のスコープの問題です。以下のように書くと、a
は変更を加えられずに済みます。
python3 -c 'a="XYZ";[print(a+b) for b in ["A","B","C"]];print(a)'
問題17
次は外部コマンドが使えない場合の問題です。ただし、私の世界線では外部コマンドが使えなくても、Pythonワンライナーだけは使えることになっています(すみません……)。ということで、とりあえずb=[a.strip() for a in open("/etc/passwd","r")]
でファイルを読み込み、c=open("a","w");[c.write(d+"\n") for d in b]
でファイルに書き込みます。
python3 -c 'b=[a.strip() for a in open("/etc/passwd","r")];c=open("a","w");[c.write(d+"\n") for d in b]'
問題18
次も外部コマンドが使えない場合の問題ですが、私の世界線ではPythonワンライナーは使えることになっているので大丈夫です(?)。まずa=[re.sub(".*:.*:.*:.*:.*:.*:","",a.strip()) for a in open("/etc/passwd","r")]
でシェルの名前だけ抜き出します。次にb=collections.Counter(a)
でシェルの個数を数えて、辞書型として保持します。最後に[[print(c,end=" "),print(b[c])] for c in b.keys()]
でシェルの名前と個数を出力します。
python3 -c 'import re,collections;a=[re.sub(".*:.*:.*:.*:.*:.*:","",a.strip()) for a in open("/etc/passwd","r")];b=collections.Counter(a);[[print(c,end=" "),print(b[c])] for c in b.keys()]'
collections.Counter
の使い方について、こちらの記事を参考にさせていただきました。ありがとうございます。
問題19
次はファイルの上書き問題です。外部コマンドを使わずにPythonワンライナーだけで(?)、文字列を置換します。a=[a.strip() for a in open("cardno","r")]
でファイルを読み込み、a=re.sub("^[0-9]*-[0-9]*","xxxx-xxxx",a[0])
で置換して、b=open("cardno","w");b.write(a+"\n")
で書き込みます。
python3 -c 'import re;a=[a.strip() for a in open("cardno","r")];a=re.sub("^[0-9]*-[0-9]*","xxxx-xxxx",a[0]);b=open("cardno","w");b.write(a+"\n")'
問題20
次は/usr
直下のファイルとディレクトリの一覧を表示する問題です。外部コマンドを使わずに解きます。そうです、もちろん、Pythonワンライナーです(?)。os.listdir()
を使うと解けます。
python3 -c 'import os;[print(a) for a in os.listdir("/usr/")]'
問題21
次もファイルの一覧を表示する問題です。今回は使用コマンドに制限はありませんので、心おきなくPythonワンライナーを書くことができます。まずa+=[b for b in glob.glob("./dir_c/**",recursive=True)]
のようにglob.glob
を使って再帰的にファイルのパスを取得します。最後に[print(b) for b in sorted(a) if os.path.isfile(b)]
でソートしてから、ディレクトリのパスを除いて出力します。
python3 -c 'import os,glob;a=glob.glob("./dir_a/*");a+=[b for b in glob.glob("./dir_b/*") if os.path.isfile(b)];a+=[b for b in glob.glob("./dir_c/**",recursive=True)];[print(b) for b in sorted(a) if os.path.isfile(b)]'
ファイルパスの取得について、こちらの記事を参考にさせていただきました。ありがとうございます。
問題22
次はダミーのFQDNを生成する問題です。random.choices
を使います。"".join(random.choices(string.ascii_letters,k=10))
でランダムに10文字の文字列を生成します。a=["com","org","net","jp"];random.choice(a);
で用意したトップレベルドメインからランダムに選択します。
python3 -c 'import random,string;a=["com","org","net","jp"];[print("".join(random.choices(string.ascii_letters,k=10))+"."+random.choice(a)) for b in range(100)]'
random.choices
について、こちらの記事を参考にさせていただきました。ありがとうございます。
ランダムな文字列の生成について、こちらの記事を参考にさせていただきました。ありがとうございます。
問題23
次はプロセスの問題です。Pythonではos.kill
でプロセスにシグナルを送れます。
python3 -c 'import os,signal;os.kill(10,signal.SIGCONT)'
プロセスについて、こちらのページを参考にさせていただきました。ありがとうございます。
問題24
次は端末が閉じられたときにtmpディレクトリ内のファイルを削除する問題です。ワンライナーで関数を定義するためにopy
を使用します。signal.signal(signal.SIGTERM,a)
でプロセスが終了したときにa
が実行されます。このワンライナーではファイルを一つだけ削除しています。
opy -m signal,time,os 'B:{def a(signum,frame): os.remove("tmp/a")};B:{signal.signal(signal.SIGTERM,a)};E:{time.sleep(100)}'
終了時の処理について、こちらの記事を参考にさせていただきました。ありがとうございます。opy
の使い方について、こちらのページを参考にさせていただきました。ありがとうございます。
問題25
次はシェルスクリプトを修正する問題です。まずpipefail.bash
の内容を読み込み、re.sub()
を使って置換して、またファイルに書き込み直します。ファイルの変更内容は解答を見ました。
python3 -c 'import re;a=open("pipefail.bash");b=a.read();a.close();b=re.sub("sort.*10","sort | head > .tmp.top10 || true",b);a=open("pipefail.bash","w");a.write(b);a.close()'
問題26
次は.bashrc
にいたずらを仕込む問題です。Pythonから直接.bashrc
に追記します。時刻指定は難しかったので、いたずらを仕掛けるだけにしました。
python3 -c 'a="""\ntrap `echo test` SIGCHLD\n""";b=open(".bashrc","a");b.write(a);b.close()'
問題27
次は実行したコマンドを修正する問題なのですが、Pythonワンライナーで解く方法が見つかりませんでした。今後の課題とさせていただきます。
問題28
次は特殊な名前のディレクトリを削除する問題です。shutil
を使えばディレクトリを削除できます。
python3 -c 'import shutil;shutil.rmtree("-Rf");shutil.rmtree("'~'")'
ディレクトリの削除について、こちらの記事を参考にさせていただきました。ありがとうございます。
問題29
次はシェルスクリプトのエラーチェック問題です。解答を見ました。Pythonからbash -n fb.bash
を実行して、そのときのエラー出力から行番号だけを抜き出します。
python3 -c 'import subprocess;subprocess.run(["bash","-n","fb.bash"])' |& python3 -c 'import re;a=input();print(re.findall("[0-9]",a)[0])'
問題30
Bashが持つ変数の一覧を表示する問題です。まず、Pythonでsubprocess.check_output()
を使ってman bash
の出力を取得します。その後、opy
やre.findall()
を使って変数の一覧だけを抜き出します。
python3 -c 'import subprocess;a=subprocess.check_output(["man","bash"]);[print(b) for b in a.split()]' | opy 'r_("BASH")' | python3 -c 'import sys,re;d=[re.findall("BASH[_A-Z]*",c)[0] for c in sys.stdin];[print(e,end=" ") for e in set(d)]'
Pythonからコマンドを実行する方法について、こちらの記事を参考にさせていただきました。ありがとうございます。
リスト内の重複除去について、こちらの記事を参考にさせていただきました。ありがとうございます。おわりに
すべてのシェル芸とPythonと読者の方々に感謝します。ありがとうございました。楽しかったです。次回の記事もよろしくお願いいたします。
スクラップ:シェル芸とPythonワンライナー
スクラップでシェル芸とPythonワンライナーの情報をまとめています。
ブログを移行しました
続きはこちらから
Discussion