シェル力が圧倒的に足りてないのでシェル力を養うために第13回シェル芸勉強会の問題に挑戦してみた。
問題はすごく工夫してあってパズル感覚で解けて楽しいしシェル力を養うのにも役立ちそう。
自分が使ってる zsh のバージョン。
$ zsh --version zsh 5.8 (x86_64-apple-darwin19.6.0)
はじめに
自分はシェル力が圧倒的に足りてないので勉強のためにシェル芸勉強会の問題に挑戦してみた。
一応自分がシェルを使う用途を書いておくと普段の仕事は Mac を使ってやっててちょっとしたことをやるのにちょろっとシェルを使う程度。
ちなみにシェル芸ってなに? ってところは Wikipedia の USP 友の会 から引用しておく。
シェル芸とは、主にUNIX系オペレーティングシステムにおいて「マウスも使わず、ソースコードも残さず、GUIツールを立ち上げる間もなく、あらゆる調査・計算・テキスト処理を CLI端末へのコマンド入力一撃で終わらせること」(USP友の会会長・上田隆一による定義[9])である。この技術を持つ人物を指すシェル芸人という呼び方も存在する[10]。
古典的には、UNIX系OSの各種CUIシェル上で単一または複数のコマンドを一行のコマンドラインでをつなぎ合わせて処理をおこなう、いわゆる「ワンライナー」系の手順にあたる。
第13回シェル芸勉強会の問題に挑戦してみた
だいぶ前だけど第12回シェル芸勉強会の問題に挑戦してみたので今回は 第13回シェル芸勉強会 の問題に挑戦してみた。
Q1
Question
次のようにShift JISのファイルを作り、Shift JISで「きく」と書いてあるファイルを探すワンライナーを考えてください。(答えは「b」ですね。)
uedambp:q1 ueda$ echo あいうえお | nkf -xLws > a uedambp:q1 ueda$ echo かきくけこ | nkf -xLws > b uedambp:q1 ueda$ echo さしすせそ | nkf -xLws > c
Answer
「きく」を検索するファイルは Shift_JIS でエンコーディングしているのを UTF-8 に変換して grep してみた。
$ ls | while read f; do cat $f | nkf -Sw | grep 'きく' >/dev/null && echo $f; done
grep の出力は /dev/null
にリダイレクトして捨てるようにしてるけど、解答例を見たら grep の -q
オプションを使ってた。
便利そうなので覚えておく。
ls | while read f; do cat $f | nkf -Sw | grep -q 'きく' && echo $f; done b
Q2
Question
次のようにディレクトリa,b,c,dに1,2,...,9というファイルがあります。各ディレクトリ内のファイル数をワンライナーで数えてください。
uedambp:q2 ueda$ tree . ├── a │ ├── 1 │ ├── 2 │ └── 3 ├── b │ ├── 4 │ └── 5 ├── c └── d ├── 6 ├── 7 ├── 8 └── 9
Answer
$ ls | while read f; do echo "$f $(ls $f | xargs -n1 | wc -l)"; done a/ 3 b/ 2 c/ 0 d/ 4
Q3
Question
今度は次のような配置でファイル1,2,...,9が置かれているときに、ワンライナーでa、cの下のファイルの総数をカウントしてください(ディレクトリを除く)。つまりaなら5個、cなら4個が正解です。
uedambp:q3 ueda$ tree . ├── a │ ├── 1 │ ├── 2 │ ├── 3 │ └── b │ ├── 4 │ └── 5 └── c └── d ├── 6 ├── 7 ├── 8 └── 9
Answer
find コマンドを -type f
オプションを指定してファイルだけ検索して uniq -c
コマンドでディレクトリごとのファイルの数を数える感じ。
$ ls | while read f; do find $f -type f | awk -F/ '{print $1}' | uniq -c; done 5 a 4 c
こちらは解答例。自分は ls コマンドを余分に実行しちゃってるけど解答例のほうは無駄がなくて分かりやすくてタイプ数も少ない。
$ find . -type f | awk -F/ '{print $2}' | uniq -c 5 a 4 c
Q4
Question
まず、次のように8桁日付のファイルを作ります。 曜日別にディレクトリを作り、その中に当該するファイルを放り込んでください。
uedambp:q4 ueda$ seq -w 1 31 | xargs -I@ touch 201401@ uedambp:q4 ueda$ ls 20140101 20140107 20140113 20140119 20140125 20140131 20140102 20140108 20140114 20140120 20140126 20140103 20140109 20140115 20140121 20140127 20140104 20140110 20140116 20140122 20140128 20140105 20140111 20140117 20140123 20140129 20140106 20140112 20140118 20140124 20140130
Answer
$ ls | while read f; do wd=$(echo $f | LANG=C gdate -f - '+%a'); mkdir $wd; mv $f $wd; done
Q5
Question
以下のようにa,b,cというディレクトリを作り、その下に「{a,b,c}数字」というファイルを作ります。ファイル名の1文字目とディレクトリ名が一致するようにファイルを移動してください。
uedambp:q5 ueda$ tree . ├── a │ ├── a01 │ └── b01 ├── b │ ├── a02 │ ├── a03 │ └── c01 └── c └── a04
Answer
$ find . -type f | awk -F/ '{print substr($3,1,1), $0}' | while read d f; do mv $f $d; done $ tree . ├── a │ ├── a01 │ ├── a02 │ ├── a03 │ └── a04 ├── b │ └── b01 └── c └── c01
Q6
Answer
次のようにディレクトリa, b, cの下に、8桁日付のファイルをいくつか置きます。 各ディレクトリの最新日付のファイルをカレントディレクトリ(a,b,cのあるディレクトリ)にコピーしてください。各ディレクトリの最新ファイルの日付はそれぞれ違い、コピーの際に衝突しないこととします。
uedambp:q6 ueda$ tree . ├── a │ ├── 20130120 │ ├── 20140901 │ └── 20141021 ├── b │ ├── 20131011 │ └── 20140202 └── c ├── 20110202 ├── 20130224 └── 20141224
Answer
ls コマンドはファイル名をソートしてから出力するので各ディレクトリを ls して最後の出力が最新日付のファイル。 tail コマンドで ls コマンドの最後の出力だけを取得してコピーする。
$ ls | while read f; do ls $f | tail -n 1 | xargs -I{} cp "$f{}" .; done $ ls 20140202 20141021 20141224 a/ b/ c/
Q7
Question
Q6について、適当にファイルをtouchします。今度はタイムスタンプが最新のファイルを、a, b, cそれぞれからカレントディレクトリにコピーしてください。コピーの際にタイムスタンプを変えない事。
Answer
適当にファイルを touch する。
$ touch a/20140901 $ touch b/20131011 $ touch c/20141224
ls コマンドの -t
オプションと cp コマンドの -p
オプションを使う。
自分はどちらのコマンドもたしかいい感じのオプションがある気がするけどよく分からないので manual を見ながらやった。
$ ls | while read f; do ls -t $f | head -n 1 | xargs -I{} cp -p "$f{}" .; done
Q8
次のように5個ファイルを作ります。file1をfile2, file2をfile3, file3をfile4, file4をfile5, file5をfile1にmvしてください。
uedambp:q8 ueda$ for i in 1 2 3 4 5 ; do echo $i > file$i ; done uedambp:q8 ueda$ head * ==> file1 <== 1 ==> file2 <== 2 ==> file3 <== 3 ==> file4 <== 4 ==> file5 <== 5
Answer
コピー元とコピー先のファイルが循環するのがポイント。
$ ls | sort -r | awk 'BEGIN{d="tmp"}{print $1,d;d=$1}END{print "tmp",d}' | while read s d; do mv $s $d; done $ head * ==> file1 <== 5 ==> file2 <== 1 ==> file3 <== 2 ==> file4 <== 3 ==> file5 <== 4