日々之迷歩

世の中わからんことだらけ

ITが複雑で難しくなっていく様に翻弄される日々です。微力ながら共著させていただいた「シェル・ワンライナー160本ノック」をよろしくお願い申し上げまする。

「第62回シェル芸勉強会」リモート参加レポート

2022-12-03(土)「第62回シェル芸勉強会」が開催された。まずは上田会長へ、問題作成と勉強会運営に感謝。参加者の皆様もお疲れ様。東京と福岡の会場は無しだが、大阪と長崎ではサテライト会場が設けられた。

勉強会募集サイト

勉強会開催アナウンス

usptomo.doorkeeper.jp

大阪サテライト会場アナウンス

shellgei-osaka.connpass.com

長崎サテライト会場アナウンス

shell-nagasaki.connpass.com

勉強会開催報告など

Togetterまとめ

togetter.com

シェル芸勉強会リンク集

【後ほど記載】

Youtubeライブ配信の録画

www.youtube.com

大阪サテライトレポート

horo1717.hatenablog.com

参加レポート

xztaityozx.hatenablog.com

TLプレゼン資料

speakerdeck.com

勉強会の内容について

毎度のことながら、問題作成と解説配信ワンオペの上田会長に感謝。 今回の問題はいろんな形式のデータファイルをいじる問題。ファイルフォーマットの仕様を調べることが必要になった。

問題と解答

問題に使うデータファイルは、下記のGitリポジトリから取得。

$ git clone https://github.com/ryuichiueda/ShellGeiData.git
$ cd ShellGeiData/vol.62

問題や解答例は、TogetterまとめやYoutubeライブ配信の録画を参照いただきたい。以下、私なりの解説を掲載する。

Q1

PowerPoint(pptx)のファイル内に含まれる文字データからunkoという文字列を作り出す問題。最初はunkoという文字列を探すと題意を間違って解釈していたが、unkoという文字を作り出せば良い。

最初はstringsコマンド使って文字列を探したが、文字を削り過ぎてunkoが見つからなかった。以下、上田会長の解答を解説する。tr -dcを使って、指定した文字(u、n、k、o)以外を削除し、grep -oで文字列unkoを切り出す。ウ○○という文字を見つける別解などは動画を参照のこと。

$ cat message.pptx | tr -dc 'unko'
nonnoknnukonooonnuuononnuukoouokukn(略)uouououououououououououououououoounuoooooooonno

$ cat message.pptx | tr -dc unko | grep -o unko
unko

Q2

ファイルの種別を調べるにはfileコマンドを使うのが常套手段。画像や動画ファイルの場合は、ImageMagickのidentifyコマンドを使うとより詳細な内容が確認可能。fileコマンドを使う方法は動画を参照のこと。

今回はそれ以外の方法で、PNG画像ファイルの仕様を調べてファイルの中身を確認する方法でやってみた。 PNGファイルの場合は、ファイル先頭8バイト分のバイナリが16進数で89504e470d0a1a0aである。

#PNGファイルの場合
$ xxd -p -l 8 img1.png
89504e470d0a1a0a

#PNGファイル以外の場合
$ xxd -p -l 8 img2.png
8e63944f83458393

grepの終了ステータスを利用して、「PNG」か「PNGじゃない」かを出力するワンライナーはこう書ける。

$ xxd -l 8 -p img1.png | grep '^89504e470d0a1a0a$' > /dev/null && echo PNG || echo PNGじゃない
PNG

$ xxd -l 8 -p img2.png | grep '^89504e470d0a1a0a$' > /dev/null && echo PNG || echo PNGじゃない
PNGじゃない

bashのforループで、全てのファイルに対して処理する解答がこちら。

$ ls *.png | while read f; do xxd -l 8 -p $f | grep '^89504e470d0a1a0a$' > /dev/null && echo $f PNG || echo $f PNGじゃない; done
img1.png PNG
img2.png PNGじゃない
img3.png PNG
img4.png PNGじゃない

Q3

PGM画像ファイルのフォーマットを調べる必要がある問題。解説の詳細は動画を参照のこと。

以下会長の解答を解説する。PGMファイルは先頭3行がASCIIデータで、4行目以降に画像データが含まれる。まずはImageMagickのconvertコマンドでPNGからPGMへ変換して先頭3行を確認する。第二引数でPGM:-のように指定すると標準出力へ出力する。

$ convert program.png PGM:- | head -n 3
P5      【←マジックナンバー(バイナリフォーマット)】
120 133 【←画像の解像度(幅、高さ)】
255     【←色の深度(最大255)】

4行目以下のデータをfileコマンドで確認すると、ELF形式の実行ファイルである可能性が高い。

$ convert program.png PGM:- | tail -n +4 | xxd
00000000: 7f45 4c46 0201 0100 0000 0000 0000 0000  .ELF............
00000010: 0300 3e00 0100 0000 6010 0000 0000 0000  ..>.....`.......
(略)

$ convert program.png PGM:- | tail -n +4 | file -
/dev/stdin: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=27036e54dcac6372cd6cffc9c84332e3f4ca5f9e, for GNU/Linux 3.2.0, not stripped

4行目以下をファイルに保存して実行すると、僕の環境ではライブラリバージョン不一致のエラーが出た。スタティックリンクした実行バイナリなら実行出来ていたと思われる。

$ convert program.png PGM:- | tail -n +4 > out; chmod +x out; ./out
./out: /lib/x86_64-linux-gnu/libc.so.6: version `GLIBC_2.34' not found (required by ./out)

Q4

今度はQ3のようなプログラムを埋め込んだ画像を作る問題。詳細は動画を参照のこと。テキストデータであるスクリプト言語なら、テキストエディタを使って作成可能である。以下手順を記載する。画像の解像度(幅と高さ)とプログラムのバイト数を一致させるのが面倒か?

# PGM形式でデータ部にシェルスクリプトを入れた画像ファイルを作成。
$ vim test.pgm
*********** test.pgm ***********
P5
10 5
255
#!/bin/bash
echo 'Hello World!!!!!!!!!!!!!!!!!!!!'
*********** test.pgm ***********

# 画像データ部のデータ容量確認
$ cat test.pgm | sed 1,3d
#!/bin/bash
echo 'Hello World!!!!!!!!!!!!!!!!!!!'

$ cat test.pgm | sed 1,3d | wc -c
50 (幅*高さと一致)

# PNG形式へフォーマット変換
$ convert test.pgm test.png

# fileコマンドでファイルの確認
$ file test.p*
test.pgm: Netpbm image data, size = 10 x 5, rawbits, greymap
test.png: PNG image data, 10 x 5, 8-bit grayscale, non-interlaced

# ImageMagickのidentifyコマンドでファイルの確認
$ identify test.p*
test.pgm PGM 10x5 10x5+0+0 8-bit Grayscale Gray 62B 0.000u 0:00.000
test.png PNG 10x5 10x5+0+0 8-bit Gray 256c 256B 0.000u 0:00.000

# PNG形式からPGM形式へフォーマット変換
$ convert test.png PGM:-
P5
10 5
255
#!/bin/bash
echo 'Hello World!!!!!!!!!!!!!!!!!!!'

# ヘッダー部を取り除いて`bash`でシェルスクリプトとして実行
$ convert test.png PGM:- | sed 1,3d | bash
Hello World!!!!!!!!!!!!!!!!!!!

Q5

PowerPoint形式のファイルの中から、記載されたテキストを抽出する問題。PowerPoint形式のファイルは、複数のXMLファイルがZIPで圧縮されて一つのファイルに結合されている。

unzip-lオプションで、ZIPファイル内のファイルリストを表示する。

$ unzip -l message.pptx 
Archive:  message.pptx
  Length      Date    Time    Name
---------  ---------- -----   ----
     3247  1980-01-01 00:00   [Content_Types].xml
      738  1980-01-01 00:00   _rels/.rels
     3497  1980-01-01 00:00   ppt/presentation.xml
     3512  1980-01-01 00:00   ppt/slides/slide1.xml
(省略) 
     182  1980-01-01 00:00   ppt/tableStyles.xml
     1113  1980-01-01 00:00   docProps/app.xml
      681  1980-01-01 00:00   docProps/core.xml
      429  1980-01-01 00:00   ppt/revisionInfo.xml
---------                     -------
    97362                     38 files

スライド1枚目の内容はppt/slides/slide1.xmlというファイルに記載されている。unzip -pでファイルの中身を全て標準出力に出力する。第二引数にファイル名を指定すると、指定したファイルの中身だけ標準出力に出力する。

$ unzip -p message.pptx ppt/slides/slide1.xml
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<p:sld xmlns:a= (略) </p:sld>

スライドに記載されたテキストは<a:t>タグ内に記載されているようなので、grep -oを使って切り出す。

$ unzip -p message.pptx ppt/slides/slide1.xml | grep -o '<a:t>[^<]*</a:t>'
<a:t>メッセージ</a:t>
<a:t>これを読んでいるころは、</a:t>
<a:t>シェル芸勉強会に参加されていることでしょう。</a:t>
<a:t>どうせ、yes ウンコとかコマンドを</a:t>
<a:t>打たさせていると思うと、大変残念です。</a:t>
<a:t>どんまいける!</a:t>

あとはタグを消去して完成。解答はこちら。

$ unzip -p message.pptx ppt/slides/slide1.xml | grep -o '<a:t>[^<]*</a:t>' | sed 's;<[^<]*>;;g'
メッセージ
これを読んでいるころは、
シェル芸勉強会に参加されていることでしょう。
どうせ、yes ウンコとかコマンドを
打たさせていると思うと、大変残念です。
どんまいける!

上田会長の解説に出てきたunzip-cオプションを使うと、ファイル名とファイルの中身を同時に出力出来るのを初めて知った。

Q6

tarアーカイブファイルの中身を覗く問題。tarコマンドの語源はtape archiveだと思うのだが、テープメディアにtarコマンドでバックアップをした事ある人はどのくらいいるのだろうか?僕はあるぞ。

小問1

余談はさておき問題を解く。以下上田会長の解答例を解説。とりあえずcatコマンドで中身を表示すると、ヌル文字(\0)のパディングは表示されず、ファイルのメタデータ(固定長のASCII)とファイルの中身が見える。

$ cat nanika_data.tar
./たーぼーる/0000775000175000017500000000000014342245663015052 5ustar  uedaueda./たーぼーる/10周年.txt0000664000175000017500000000002014342245627020203 0ustar  uedaueda記念なので
./たーぼーる/うんこ.txt0000664000175000017500000000000714342245701020611 0ustar  uedaueda贈呈
./たーぼーる/シェル芸.txt0000664000175000017500000000001214342245223022016 0ustar  uedaueda勉強会

sedコマンドでファイルのメタデータを行頭に持ってくる。

$ cat nanika_data.tar | sed 's;./たー;\n&;g'

./たーぼーる/0000775000175000017500000000000014342245663015052 5ustar  uedaueda
./たーぼーる/10周年.txt0000664000175000017500000000002014342245627020203 0ustar  uedaueda記念なので

./たーぼーる/うんこ.txt0000664000175000017500000000000714342245701020611 0ustar  uedaueda贈呈

./たーぼーる/シェル芸.txt0000664000175000017500000000001214342245223022016 0ustar  uedaueda勉強会

ファイルを最終更新時刻で並べ替えるため、ファイルのメタデータ部分を最終更新時刻より前と後に分割する。最終更新時刻は137バイト目以降なので、sedの拡張正規表現を使って(-Eオプションを利用)先頭から137バイト目にスペースを挿入する。

$ cat nanika_data.tar | sed 's;./たー;\n&;g' | LANG=C sed -E 's/^.{136}/& /'

./たーぼーる/00007750001750000175000000000000 14342245663015052 5ustar  uedaueda
./たーぼーる/10周年.txt00006640001750000175000000000020 14342245627020203 0ustar  uedaueda記念なので
 
./たーぼーる/うんこ.txt00006640001750000175000000000007 14342245701020611 0ustar  uedaueda贈呈
 
./たーぼーる/シェル芸.txt00006640001750000175000000000012 14342245223022016 0ustar  uedaueda勉強会

sortコマンドで2列目の最終更新時刻で並べ替えて、改行やヌル文字などのゴミを除去して完成。

$ cat nanika_data.tar | sed 's;./たー;\n&;g' | LANG=C sed -E 's/^.{136}/& /' | sort -k2,2n | grep -a txt | tr '\0' ' ' | awk '{print $1,$NF}'
./たーぼーる/シェル芸.txt 勉強会
./たーぼーる/10周年.txt 記念なので
./たーぼーる/うんこ.txt 贈呈

小問2

小問1の出力結果から、ファイル名のディレクトリ部分と拡張子部分を削除して完成。

$ cat nanika_data.tar | sed 's;./たー;\n&;g' | LANG=C sed -E 's/^.{136}/& /' | sort -k2,2n | grep -a txt | tr '\0' ' ' | awk '{print $1,$NF}' | sed 's;.*/;;' | sed 's/\.txt //g'
シェル芸勉強会
10周年記念なので
うんこ贈呈

終わりに

参加レポート記事を書く際に、詳細に書こうとすればするほど時間がかかる。どこまで書けるは時間次第なところではあるが、今後も可能な限りは参加レポート書いて行こうと思う。 今回もLTや二次会も面白かった。会長が問題作成や配信準備、動画編集などされているのもそうだが、かなりの手間や時間がかかっているんだろうなあと痛感した。

自宅から勉強会参加する際はMacbook Proに外付けディスプレイをつなげて使っているのだが、最近外付けディスプレイが2枚に増えた。非常に快適で、サテライト会場開催時にも外付けディスプレイ2枚の環境が欲しいと思う今日この頃である、、、