第30回シェル芸勉強会参加報告
2017-08-26(土)に開催された、第30回シェル芸勉強会(jus共催 第30回危念シェル芸勉強会)に参加しました。
ただ、在住地の関係で直接会場へ伺うことは出来なかったため、Youtubeで配信してくださったライブ中継を利用して、遠隔で参加しました。
勉強会は、午前と午後の2部構成でした。
午前は、シェルに関する勉強会で、Perlの正規表現に関する内容でした。
午後は、シェル芸勉強会で、シェル芸力を付けるための問題を解きました。
以下、勉強会の詳細を記します。
実行環境
Arch Linux 4.12.8-2-ARCH
GNU bash 4.4.12
GNU coreutils 8.27-1
GNU findutils 4.6.0-2
grep (GNU grep) 3.1
sed (GNU sed) 4.4
gawk 4.1.4
pandoc 1.19.2.1
Gnu Octave 4.2.1
シェルに関する勉強会
USP友の会 鳥海 秀一さんによる、Perlワンライナー入門でした。
今回の講義は、前回第一回の続きで、Perlの正規表現に関する内容でした。
以下、講義で学んだこと・気になったことを記します。
Perlにおける正規表現は、変数に代入して、変数展開して使用することができる。
丸括弧を入れ子(グループ化)にした場合、外側から順に評価され、特殊変数に代入される。
echo abcdef | perl -lne '/((.)..)/ and print "$1 $2"'
abc a
丸括弧の入れ子は、BREやEREでも可能。
→ ただ、色々と勘違いしていて、混乱していた…
あああ… 今やっと理解できました! 単純にマッチした部分の出力箇所を、後方参照で入れ替えているだけなんですね。\2の直後に、マッチしなかった部分がそのまま出力されているだけとorz
— ginjiro (@gin_135) 2017年8月26日
echo 12345 | sed -r 's/(.)(..)/\1 \2@/'
1 23@45
...という事は、グループ化の丸括弧を入れ子にするのは、Perlの正規表現に限らず、BREやEREでも可能なのね。でも、可読性がかなり悪くなるし、真面目な処理では出来るだけ使わないほうが良いかも... #シェル芸
— ginjiro (@gin_135) 2017年8月26日
キャプチャなし括弧(グループ化のみ括弧)は、後方参照用の変数を使用しない。 → 量指定子を使用するためにグループ化したい場合は、このキャプチャなし括弧を使用すれば、後方参照用変数の名前を節約できる。
echo 12345 | perl -nle '/(..)(.)(..)/ and print "$1 $2 $3"' # キャプチャ無し括弧
12 3 45
echo 12345 | perl -nle '/(..)(?:.)(..)/ and print "$1 $2 $3"' # キャプチャ無し括弧
12 45
丸括弧の入れ子を調べて脱線しすぎて、これ以外の内容はさっぱり追いつけず…
第30回シェル芸勉強会
問題文および模範解答は、以下のURLから。
https://blog.ueda.asia/?p=10134
問1
ディレクトリ配下にある全ファイルを探索し、各ファイルの中から初めて"Keywords:"に一致するレコードのみを出力する問題。
find
コマンドで該当ファイルを列挙した後、各ファイルから grep
で該当レコードを抽出していく。
このとき、「マッチした行数が n
に達したらファイルの読み込みを中止する」オプション、 -m
を使うのがポイント。
このオプションの引数に 1
を指定しておくことで、各ファイルにつき、"Keywords:"レコードに一致する度に、次のファイルまで処理をスキップしてくれる。
#!/bin/sh
find posts -type f -name '*.md' | # posts以下から、全Markdownファイルを列挙
xargs grep -m 1 'Keywords:' | # 各ファイルから、"Keywords:"を含むレコードを出力
# 一致レコードが1つでも見つかったら、次のファイルを探索
sed 's/:/ /1' | # ファイルパスと一致レコードの区切り文字をスペースに変換
sed 's:.*/\([^/]*\)/[^ ]*:\1:' | # ファイルパスを第2層目のディレクトリ名のみに変換
sort # 出力のソート
以下、コピペ実行用のワンライナーコード。
find posts -type f -name '*.md' | xargs grep -m 1 'Keywords:' | sed 's/:/ /1' | sed 's:.*/\([^/]*\)/[^ ]*:\1:' | sort
問2
HTMLソースコードのうち、リンクが相対パスになっている箇所に、パスを付加する問題。
ギリギリ時間以内に解けた問題…
元ソースから、置換すべき箇所を特定し、置換コマンドを生成して実行してみた。
今考えると、 sed
で少しずつ加工していく方が、分かりやすかったかもしれない。
#!/bin/sh
cat url.html | # HTMLソースの出力
grep -oE '(href|src)="[^"]*"' | # hrefもしくはsrcを含む箇所の抽出
grep -vE 'https?|"/' | # 絶対パス表記になっているレコードの除去
gawk '{print $0, gensub(/"[./]*/, "\"/file/", "1")}' | # 置換前および置換後の文字列の出力
awk 'BEGIN{printf "sed "} {printf "-e '\''s@"$1"@"$2"@'\'' "} END{print "url.html"}' | # 該当箇所の置換コマンドの生成
sh # 置換の実行
以下、コピペ実行用のワンライナーコード。
cat url.html | grep -oE '(href|src)="[^"]*"' | grep -vE 'https?|"/' | gawk '{print $0, gensub(/"[./]*/, "\"/file/", "1")}' | awk 'BEGIN{printf "sed "} {printf "-e '\''s@"$1"@"$2"@'\'' "} END{print "url.html"}' | sh
問3
テキストファイルから、HTMLコードを出力する問題。
よくよく見ると、元ファイルは軽量マークアップ言語のフォーマットをしている。
そこで、 pandoc
を使うことは思いついたのだけれど…
単に pandoc
で変換するだけでは、部分的なHTMLコードしか出力されず、ヘッダ部分を付加する方法が分からなかった。
ヘッダを出力するには、出力形式オプションに -t html5
を指定すればOKだそう。asciidoc
でも試したけれど、<li>タグの中身に<p>タグが入っていたり、ちょっと加工が大変な感じ。
#!/bin/sh
cat list | # 元ファイルの出力
pandoc -f markdown -t html5 -s | # HTMLコードへの変換
sed '5,11d' # 不要レコードの除去
以下、コピペ実行用のワンライナーコード。
cat list | pandoc -f markdown -t html5 -s | sed '5,11d'
問4
CLIでGitHubにリポジトリを作成し、テキストファイルをpushする問題。
GitHubでは、リポジトリを操作するためのAPIを公開しているので、これを使うのかと思ったけれど…
https://developer.github.com/v3/repos/#create
そもそも、 git
自体の使い方を知らなかったので、全く太刀打ち出来なかったorz
この問題では、GitHubをCLIから操作できるコマンド、 hub
を使用するとのこと。
https://hub.github.com/
自力では解けなかったので、解答は無し。
問5
複素数の乗算をする問題。
awk
や bc
で出来ないかと考えていたのだけれど…
どうやら組み込み機能では出来ないようだったので、他のソフトウェアに頼ることにした。
計算式を生成した後、数値解析向けのプログラム octave
に計算してもらう。
#!/bin/sh
cat complex | # 元ファイルの出力
sed 's/.*/(&)/' | # レコード毎に丸括弧を付加
sed -z 's/\n/*/1' | # 計算式の生成
octave -q 2>/dev/null # 計算の実行
以下、コピペ実行用のワンライナーコード。octave
を実行した際、ワーキングディレクトリに octave-workspace
というファイルが生成されてしまうので、最後に削除している。
cat complex | sed 's/.*/(&)/' | sed -z 's/\n/*/1' | octave -q 2>/dev/null && rm octave-workspace
問6
フィボナッチ数列から、条件に一致する数値を出力する問題。
前半は難しい問題ばかり続いていただけに、やっとシンプルな問題!
フィボナッチ数列を出力し、6765までの出力のみを抽出した後、目的の数値のみを出力すればOK。
#!/bin/sh
awk 'BEGIN{a=0; b=1; while(1){print c=a; a=a+b; b=c}}' | # フィボナッチ数列の出力
sed '/6765/q' | # 6765までの抽出
tac | # 出力の反転
sed -n '5p' # 6765から4つ前の数値の出力
以下、コピペ実行用のワンライナーコード。
awk 'BEGIN{a=0; b=1; while(1){print c=a; a=a+b; b=c}}' | sed '/6765/q' | tac | sed -n '5p'
問7
00から99までの数値のうち、ある数列に含まれていないものを列挙する問題。
パッと思いついたところ、解答方針は2つ。
元の数列を2桁の数値に分解し、00から99までの数列と比較する
00から99までの各数値それぞれについて、元数列と一致するかどうかを調べる
このうち、方針1は、元数列を分解するのが面倒そう。
(方針1は、元数列が"1234"の場合、"12", "23", "34"と分解する必要があるので。)
そこで、方針2、00から99の各数値について、元数列に含まれているかどうかを調べる方法にした。
ただ、今回の解法コードだと、00から99までの100回分コマンドが実行されてしまうので、速度はあまり宜しくないかも…
#!/bin/sh
seq -w 0 99 | # 00から99までの数列の出力
sed 's:.*:grep & nums >/dev/null || echo &:' | # 各数列に対して、元数列に含まれているかどうか判別するコマンドを生成
sh # 各コマンドの実行
以下、コピペ実行用のワンライナーコード。
seq -w 0 99 | sed 's:.*:grep & nums >/dev/null || echo &:' | sh
問8
複数のアルファベット区間のうち、含まれているアルファベットが一番多い区間を求める問題。
こういうシンプルな問題、大好きです…!!
方針は、指定されたアルファベット区間を各文字に展開し、その文字数を求めていく。
ただ、区間を文字に展開する処理が、ちょっと面倒臭い…
そこで、 bash
のブレース展開を利用して、アルファベット区間を展開してやる。
各区間毎に展開するために、レコード毎に展開コマンドを生成し、 bash
に実行させる。
ブレース展開した後は、 awk
等でフィールド数を計算し、最大値を求めればOK
#!/bin/sh
cat alphabet | # 元ファイルの出力
sed 's/-/../' | # 範囲指定子を".."に変更
sed 's/.*/echo & {&}/' | # ブレース展開出力コマンドの生成
bash | # ブレース展開の実行
### 1:アルファベット区間 2/NF:区間に含まれる各アルファベット
sed 's/[.][.]/-/' | # 範囲指定子を"-"に変更
awk '{print $1,NF-1}' | # 各レコードについて、アルファベット数を出力
### 1:アルファベット区間 2:区間に含まれるアルファベット数
sort -k 2,2nr | # 第2フィールドをキーとして数値ソート
head -n 1 # 第2フィールドが最大のレコードの抽出
以下、コピペ実行用のワンライナーコード。
cat alphabet | sed 's/-/../' | sed 's/.*/echo & {&}/' | bash | sed 's/[.][.]/-/' | awk '{print $1,NF-1}' | sort -k 2,2nr | head -n 1
参照Webサイト
jus共催 第30回危念シェル芸勉強会
→ 勉強会の概要。シェル芸 のライブ ストリーム(午後)
→ 今回の勉強会の動画配信(アーカイブ)。
雑記
最近、シェル上で操作をする機会が減ってしまったためか、今回の問題は解けない物が幾つかあった。
問題自体が難しかった事もあるのだろうけれど、テキスト操作の問題すら、苦戦してしまった…
ここ数ヶ月、あまりに堕落しきっていたので、自分の今後のためにも、どうにかしたいところ。
勉強会を開催してくださった、上田会長をはじめとした日本UNIXユーザ会とUSP友の会の皆様、ありがとうございました!!