2017年8月26日

第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でも可能。
    → ただ、色々と勘違いしていて、混乱していた…​

  • キャプチャなし括弧(グループ化のみ括弧)は、後方参照用の変数を使用しない。 → 量指定子を使用するためにグループ化したい場合は、このキャプチャなし括弧を使用すれば、後方参照用変数の名前を節約できる。

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

複素数の乗算をする問題。

awkbc で出来ないかと考えていたのだけれど…​
どうやら組み込み機能では出来ないようだったので、他のソフトウェアに頼ることにした。
計算式を生成した後、数値解析向けのプログラム 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つ。

  1. 元の数列を2桁の数値に分解し、00から99までの数列と比較する

  2. 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サイト

雑記

最近、シェル上で操作をする機会が減ってしまったためか、今回の問題は解けない物が幾つかあった。
問題自体が難しかった事もあるのだろうけれど、テキスト操作の問題すら、苦戦してしまった…​
ここ数ヶ月、あまりに堕落しきっていたので、自分の今後のためにも、どうにかしたいところ。


勉強会を開催してくださった、上田会長をはじめとした日本UNIXユーザ会とUSP友の会の皆様、ありがとうございました!!

Tags: シェル芸 Unix
このエントリーをはてなブックマークに追加