.
AtCoder Beginner Contest の A 問題がシェル芸の練習にちょうどよいことに気づいたのでいくつか問題を紹介します。
今回は awk は使用禁止とします。
フォーマットは @kotatsugame さんの https://qiita.com/kotatsugame/items/184bfd63d9b21f214475 を参考にしています。
zshでのみ動くコードが多いです bashでも動くように修正中です
ABC077A - Rotation
$C_{11}C_{12}C_{13}$
$C_{21}C_{22}C_{23}$
この長方形を180度回転させて元の長方形に一致するかを判定します。
bashでは動きません
read s;echo $s|rev|sed -e "/`cat`/cYES" -e "cNO"
read sで一行目を読み込み、一行目を反転した結果(rev)が残りの標準入力と一致するならYES、そうでないならNOを出力します(sed)。
sedの部分は`cat`の結果に入力がマッチした場合には行をYESに置換、そうでない場合はNOに置換します。
cat は dd でも代用できますが可読性が下がりすぎるのでこの記事では cat を使うことにします。
ABC119A - Still TBD
$S$
yyyy/mm/ddで入力される文字が2019/04/30より前か判定します。
(($(date -d `cat` '+%s')>`date -d 2019/4/30 '+%s'`))&&echo TBD||echo Heisei
今回の制約に$S$が2019年の日付とあるのでコードをより短くすることは可能ですが実用性が高い方法で実装しました。
$(hogehoge)と`hogehoge`は同じ意味で、hogehogeの実行結果を展開する記法ですが、ネストする場合は外側は$(hogehoge)にする必要があります。(``hogehoge``のようにすると前2つと後ろ2つで区切られてしまうため)
dateコマンドの-dは現在時刻の代わりに引数の文字列を変換して出力するオプションです。'+%s'は日時ををエポック秒に直します。
2019/4/30と入力のエポック秒を比較して入力のほうが大きいならTBD、そうでないならHeiseiと出力します。短絡評価というやつです。
ABC197A - Rotate
$S$
長さ3文字の$S$の先頭の文字を$S$の末尾に移動して出力します
sed -E 's/(.)(.*)/\2\1/'
sed入門です。
sコマンドで一文字とその後の文字にマッチして、一文字目以外の文字を最初に、最後に一文字目の文字を出力しています。
(hoge)は-Eで使える拡張正規表現の機能で、hogeにマッチした文字を\1,\2...に保存します。
なお、sedは最長一致なので^,$などを指定する必要はありません。
ABC248A - Lacked Number
$S$
0から9までのうち9種類の文字が含まれる文字列$S$に唯一存在しない0から9までのうちの文字を出力します。
bashでは動きません
seq 0 9|tr -d `cat`
seq 0 9で改行区切りで0から9までの文字を出力し、それのうち入力に含まれる文字をtrの-dオプションで削除します。
なお AtCoder は基本余計な改行空白があっても正解と判定されるのでこのコードでも正解できますがそうでない場合は\nもtrコマンドの削除する文字に含める必要があります。
ABC271A - 484558
$N$
2 桁の 16 進表記に10進数で与えられる$N$を変換します。
printf '%02X' `cat`
printfコマンドの便利な機能です。
%02Xの02は2桁に自動でパディングすること、Xは英大文字の16進数で数字を表示することを示しています(xだと小文字で表示される)。
似たような記法が他の言語でも使えることが多いので覚えておくと便利です。
ABC279A - wwwvvvvvv
$S$
wとvからなる文字列$S$に、いくつ下に尖っている箇所があるかをカウントします。
sed 's/w/vv/g'|wc -L
sedでwを適当な二文字に変えてwc -Lで文字数をカウントします。
-Lオプションは文字列の中で最も長い行のバイト数を出力するものです。
なお、-cでカウントしようとすると改行がカウントされてしまい最初に改行を消すかあとからカウントから 1 を引く必要があるので注意が必要です。
ABC281A - Count Down
$N$
$N$ 以下の非負整数を降順にすべて出力します。
echo {`cat`..0}
シェルの展開の機能を使います。
{n..m}はnからmまでの数字をスペース区切りに並べたものへ展開されます。
追記: このコードはシェルによっては動きません。zsh, ksh では動きましたが sh, bash では動きませんでした。@ko1nksm さんに指摘をいただきました。
原因の説明 長いです
原因は bash ではシェルの展開の順序が
The order of expansions is: brace expansion; tilde expansion, parameter and variable expansion, arithmetic expansion, and command substitution (done in a left-to-right fashion); word splitting; and pathname expansion.
と、brace expansion の方が command substitution よりも優先度が高いことにより、{`cat`..0}が先に無効な brace expansion としてパースされ、展開されずにあとから cat の実行結果に置き換わることです。
なお zsh で動作するのは、展開の順序が
The following types of expansions are performed in the indicated order in five steps:
- History Expansion
(説明略)- Alias Expansion
(説明略)- Process Substitution
Parameter Expansion
Command Substitution
Arithmetic Expansion
Brace Expansion
(説明略、5つは優先順位が同じで左から右の順に実行される/同一単語の場合は上から順に実行される)- Filename Expansion
(説明略)- Filename Generation
(説明略)
となっており、Command Substitution と Brace Expansion の優先順位が同じであること、また、ネストされている場合は内側が先に展開されること(この部分の似たような説明はいくつかあったのですが Command Substitution に関する記述が見つかりませんでした 知っている方がいれば教えてください)により、先にcatが実行され正しくn n-1 n-2 ... 0 と展開されるからです。
説明終わり
bash でこの方法を使うには、
eval "echo {`cat`..5}"
のように二段階で展開させる必要があります。(シェル側でcatを展開し、evalで{n..m}を展開する)
なお、seqコマンドを使った
seq `cat` -1 0
でも正解できます。こちらは環境に関係なく動作します。(スペース区切りが改行区切りになります)
ABC282A - Generalized ABC
$K$
Aから順番に英大文字を$K$個連続で出力します。
bashでは動きません
echo {A..Z}|tr -d ' '|head -c `cat`
展開part2です。
実は{x..y}はアルファベットも展開することが可能です。
AからZまでの文字を展開してtrでスペースを除いてheadで最初の$K$文字を出力しています。
trコマンドの-cオプションは文字列のバイト数を出力するオプションです。
ABC284A - Sequence of Strings
$N$
$S_1$
$S_2$
$\vdots$
$S_N$
文字列の配列$S$を逆順に出力します。
sed 1d|tac
sedで一行目の邪魔な$N$を削除、tac で逆順に$S$を出力しています。
sedの1dは、1行目をdeleteした文字列を出力し直すものです。
tacは最後の行から逆に最初の行までを出力するコマンドです。
似たコマンドとしてrevがあり、こちらは文字列全体を逆から出力します。(例:hello→olleh)
ABC299A - Treasure Chest
$N$
$S$
2つの|と1つの*、残りは.で構成される文字列$S$で*が2つの|の間にあるならinないならoutを出力します。
tr -cd '|*'|sed -e '/|\*|/cin' -e 'cout'
trで|、*以外の文字を削除してからsedで文字列に|*|が含まれるならin、そうでないならoutを出力しています。
trの-cオプションは指定された文字以外を置換するオプションです。(この場合は-dもついているので削除されます)
tr -cd '|*'はtr -d '0-9.\n'でもこの場合は代替が可能です。
ABC292A - CAPS LOCK
$S$
英小文字からなる$S$をすべて大文字に変換します。
tr a-z A-Z
trコマンドはtr abcdefghijk...と入力せずともa-z、A-Z、0-9などで置換する文字を一気に指定することができます。
これは使う機会がかなり多いので覚えておくと便利です。
ABC338A - Capitalized?
$S$
$S$の一文字目が大文字、それ以降が小文字ならYes、そうでないならNoを出力します。
sed -e '/^[A-Z][a-z]*$/cYes' -e 'cNo'
^で行頭、[A-Z]で行頭の次の文字が大文字であること、[a-z]*でその後の文字が0個以上の小文字の繰り返しであること、$で行末を指定しています。
この問題には関係ありませんが、Vimの^は空白を除いた行頭、正規表現の^は空白を除かない行頭であることに注意してください。
ABC339A - TLD
$S$
英小文字と.からなる文字列$S$の最後の.以降の文字列を出力します。
grep -o '[^.]*$'
-oはマッチした部分のみを出力するオプションです。正規表現は[^.]で.以外の文字、*で繰り返し、$で行末を示しています。
また、
tr . '\n'|tail -1
でも正解することができます。
これは.を\n、つまり改行に変換して最後の一行をtailコマンドで出力しています。