16コマ目
解説
大野先輩のこのポーズ(手のひらを差し出す)は、人差し指を立てるポーズの次に頻出な気がします。
そして、sakuraさんの15日目のご参加ありがとうございます! せっかくなので自分もこの課題に挑戦してみました。awkは分からないので(ひどい)、けっこう力業です。
#!/bin/bash
# 冒頭の説明文を保持しておく。
description=''
while read line
do
# 空行が表れたら説明文の終わり。
[ "$line" = '' ] && break
description="$description$(echo "$line")"
done
# レコード部分格納用の一時ファイルを作成する。
# (一時ファイルを使うのは、その方が楽だから)
records_path=$(mktemp)
# 各フィールドを保持する変数を初期化する。
title=''
metadata=''
url=''
# 後でソート等の処理をしやすいように、
# 複行レコードを一行レコードに変換する。
# sed: フィールドの区切りにのみスペースが表れるようにするために、
# 「%」をエスケープした上でスペースもエスケープしておく。
# ここでまとめてやっておくと、1レコードごとにsedするより
# 効率が良い(はず)。
sed -e 's/%/%25/g' -e 's/ /%20/g' |
while read line
do
if [ "$line" = '' ] # 空行が表れたらレコードの終わり。
then
# titleが空でなければレコードの情報があるので出力。
if [ "$title" != '' ]
then
# echo: 後処理のことを考えて、各フィールドを
# スペース区切りで、且つ逆順にして出力する。
# >>: 変換結果を1レコードとして一時ファイルに
# 追記する。
echo "$url $metadata $title" \
>> "$records_path"
fi
# 各フィールドの値を空に戻す
title=''
metadata=''
url=''
elif echo "$line" | egrep '^https?:' 2>&1 >/dev/null
then
# http:またはhttps:で始まる行はURLとみなす。
url="$line"
elif [ "$title" = '' ]
then
# titleが空なら、最初のフィールドなので
# それはタイトルとみなす。
title="$line"
else
# タイトルの後に現れたフィールドでURLではない物は
# カテゴリ情報とみなす。
metadata="$line"
fi
done
# ここからは出力部。
# まず説明文を出力。
echo "$description"
echo ''
# レコード部分を出力
# sort: 3番目のフィールド(タイトル)昇順
# →2番目のフィールド(カテゴリ)降順
# →1番目のフィールド(URL)降順
# とソートする。
# uniq: 2番目までのフィールドをスキップして、
# 3番目のフィールド(=タイトル)が重複している
# 物を削除。
# while: 以上の結果を1レコードずつ取り出して処理する。
cat "$records_path" |
sort -k 3,3 -k 2,2r -k 1,1r |
uniq -f 2 |
while read line
do
# tr: フィールドの区切りを改行に戻し、
# grep: 空行を削除して(「.」にマッチする=何かある行)
# tac: 逆順に並べ替えて、
# (これで、タイトル→カテゴリ→URL の順に戻る)
# sed: エスケープした文字を元にす。
echo "$line" |
tr ' ' '\n' |
grep '.' |
tac |
sed -e 's/%20/ /g' -e 's/%25/%/g'
# レコード区切りの空行を出力。
echo ''
done
# 一時ファイルを削除
rm -rf "$records_path"
説明のためのコメントを入れたり読みやすさのためにマメに改行したりしているので縦に長いですが、詰めれば30行くらいのスクリプトです。
#!/bin/bash
description=''
while read line; do
[ "$line" = '' ] && break
description="$description$(echo "$line")"
done
records_path=$(mktemp)
title=''; metadata=''; url=''
sed -e 's/%/%25/g' -e 's/ /%20/g' | while read line; do
if [ "$line" = '' ]; then
[ "$title" != '' ] && echo "$url $metadata $title" >> "$records_path"
title=''; metadata=''; url=''
elif echo "$line" | egrep '^https?:' 2>&1 >/dev/null; then
url="$line"
elif [ "$title" = '' ]; then
title="$line"
else
metadata="$line"
fi
done
echo "$description"
echo ''
cat "$records_path" | sort -k 3,3 -k 2,2r -k 1,1r | uniq -f 2 | while read line; do
echo "$line" | tr ' ' '\n' | grep '.' | tac | sed -e 's/%20/ /g' -e 's/%25/%/g'
echo ''
done
rm -rf "$records_path"
これを「sort_recon_list.sh」みたいな名前で保存してchmod +x sort_recon_list.sh
で実行権限を付けてcat recon_list | ./sort_recon_list.sh
とすると課題の答えが出力されると思うのですが、どうでしょうか?(要求仕様を見落としてたらすみません!)
基本戦略としては、レギュレーションに触れない範囲で、問題を解きやすい形に変換するという方針でやってみました。
- sortコマンドは一行が1レコードならフィールドをソートキーにできるので、各レコードは一旦1行にまとめて、最後に元の形に戻す。
- uniqコマンドはフィールドの区切り文字を指定できず、必ずスペースかタブによるフィールド区切りを要求するので、データ内のスペースは一旦すべてエスケープし、スペースはフィールドの区切り文字としてだけ登場するようにする。
- タブ文字を区切りに使おうと思うと、echoではタブ文字を出力できなかったりsortコマンドの区切り文字の指定を工夫したりしないといけないので、タブ文字の使用自体を避けると色々楽になる。
- uniqコマンドは重複チェックの対象のフィールドの位置を「何番目のフィールド以降か」でしか指定できないので、重複チェックの対象にしたいフィールド(タイトル)が最後のフィールドになるように並べ替える。またその時は、最後にtacで逆順に並べ替えれば元の形に戻るように、URL→カテゴリ情報→タイトル と元の完全な逆順にしておく。
「扱いにくいデータを扱いにくい形式のまま操作するよりは、レギュレーションが許す限り扱いやすい形に寄せていく」というのが、プログラミングでは一般的に言えるセオリーだと思います。ここでは「Bashスクリプトで」というレギュレーションだけ課すことにして、それ以外は自由にやってみました。もちろん、言語の縛りが無いならRubyなどの高機能な言語を使った方がきっと楽ですけどね!