👋

Pythonワンライナーで解く『シェル・ワンライナー160本ノック』第2章(問題12から問題30まで)

2023/01/20に公開

はじめに

この記事では、書籍『シェル・ワンライナー160本ノック』の問題をPythonワンライナーで解いていきます。今回は第2章の問題を解きます。Pythonワンライナー以外に縛りはしないので、execopyも使わせていただこうと思います。何卒よろしくお願いいたします。

対象読者

シェル芸とPythonを愛する人々に読んでいただけますと幸いです。

注意

私のPythonワンライナーが正しい保証はありませんので、ご注意くださいませ。また、外部コマンドを使わずに解く問題でも、Pythonワンライナーを使いますので、ご了承くださいませ。

書籍情報

書籍『シェル・ワンライナー160本ノック』のページです。
https://gihyo.jp/book/2021/978-4-297-12267-6
書籍『シェル・ワンライナー160本ノック』のGitHubのページです。
https://github.com/shellgei/shellgei160

参考記事

Pythonワンライナーの記事です。参考にさせていただきました。ありがとうございます。
https://b.ueda.tech/?page=python_shellgei

問題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

引数の取り方について、こちらの記事を参考にさせていただきました。ありがとうございます。
https://qiita.com/taashi/items/07bf75201a074e208ae5

問題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()")'

ファイルの有無の確認について、こちらの記事を参考にさせていただきました。ありがとうございます。
https://python.keicode.com/lang/file-exists.php
ファイルの作成方法について、こちらの記事を参考にさせていただきました。ありがとうございます。
https://jp.moyens.net/android/200486/

問題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の使い方について、こちらの記事を参考にさせていただきました。ありがとうございます。
https://note.nkmk.me/python-lambda-usage/
lambda内で複数の処理を実行する方法について、こちらの記事を参考にさせていただきました。ありがとうございます。
https://qiita.com/ysKuga/items/9e60a1b4aefa48febd30

問題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("")'

文字列の大文字への変換について、こちらの記事を参考にさせていただきました。ありがとうございます。
https://www.javadrive.jp/python/string/index12.html

問題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の使い方について、こちらの記事を参考にさせていただきました。ありがとうございます。
https://note.nkmk.me/python-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)]'

ファイルパスの取得について、こちらの記事を参考にさせていただきました。ありがとうございます。
https://python-work.com/file-get-list/

問題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について、こちらの記事を参考にさせていただきました。ありがとうございます。
https://note.nkmk.me/python-random-choice-sample-choices/
ランダムな文字列の生成について、こちらの記事を参考にさせていただきました。ありがとうございます。
https://qiita.com/Scstechr/items/c3b2eb291f7c5b81902a

問題23

次はプロセスの問題です。Pythonではos.killでプロセスにシグナルを送れます。

python3 -c 'import os,signal;os.kill(10,signal.SIGCONT)'

プロセスについて、こちらのページを参考にさせていただきました。ありがとうございます。
https://psutil.readthedocs.io/en/latest/

問題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)}'

終了時の処理について、こちらの記事を参考にさせていただきました。ありがとうございます。
https://qiita.com/qualitia_cdev/items/f536002791671c6238e3
ファイルの削除について、こちらの記事を参考にさせていただきました。ありがとうございます。
https://python.keicode.com/lang/file-delete.php
opyの使い方について、こちらのページを参考にさせていただきました。ありがとうございます。
https://github.com/ryuichiueda/opy/blob/master/EXAMPLES.md

問題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("'~'")'

ディレクトリの削除について、こちらの記事を参考にさせていただきました。ありがとうございます。
https://www.javadrive.jp/python/file/index8.html

問題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の出力を取得します。その後、opyre.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からコマンドを実行する方法について、こちらの記事を参考にさせていただきました。ありがとうございます。
https://qiita.com/tdrk/items/9b23ad6a58ac4032bb3b
リスト内の重複除去について、こちらの記事を参考にさせていただきました。ありがとうございます。
https://note.nkmk.me/python-list-unique-duplicate/

おわりに

すべてのシェル芸とPythonと読者の方々に感謝します。ありがとうございました。楽しかったです。次回の記事もよろしくお願いいたします。

スクラップ:シェル芸とPythonワンライナー

スクラップでシェル芸とPythonワンライナーの情報をまとめています。
https://zenn.dev/yusukekato/scraps/9db826dd932f1b

ブログを移行しました

続きはこちらから
https://yusukekato.jp/20230418.html

Discussion