日々之迷歩

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

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

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

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

勉強会の情報

勉強会主催者の上田さんが公開されているリンク集をご覧ください。

b.ueda.tech

勉強会の内容について

今回の問題はいろんな形式のデータファイルをいじる問題。ファイルフォーマットの仕様を調べることが必要になりました。

問題と解答

問題に使うデータファイルは、出題者上田さんが公開している下記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枚の環境が欲しいと思う今日この頃です。