全力で怠けたい

怠けるために全力を尽くしたいブログ。

シェル力が圧倒的に足りてないのでシェル力を養うために第9回シェル芸勉強会の問題に挑戦してみた。

シェル力が圧倒的に足りてないのでシェル力を養うために第9回シェル芸勉強会の問題に挑戦してみた。

問題はすごく工夫してあってパズル感覚で解けて楽しいしシェル力を養うのにも役立ちそう。

自分が使ってる zsh のバージョン。

$ zsh --version
zsh 5.8 (x86_64-apple-darwin19.6.0)

はじめに

自分はシェル力が圧倒的に足りてないので勉強のためにシェル芸勉強会の問題に挑戦してみた。

一応自分がシェルを使う用途を書いておくと普段の仕事は Mac を使ってやっててちょっとしたことをやるのにちょろっとシェルを使う程度。

ちなみにシェル芸ってなに? ってところは WikipediaUSP 友の会 から引用しておく。

シェル芸とは、主にUNIXオペレーティングシステムにおいて「マウスも使わず、ソースコードも残さず、GUIツールを立ち上げる間もなく、あらゆる調査・計算・テキスト処理を CLI端末へのコマンド入力一撃で終わらせること」(USP友の会会長・上田隆一による定義[9])である。この技術を持つ人物を指すシェル芸人という呼び方も存在する[10]。

古典的には、UNIX系OSの各種CUIシェル上で単一または複数のコマンドを一行のコマンドラインでをつなぎ合わせて処理をおこなう、いわゆる「ワンライナー」系の手順にあたる。

第9回シェル芸勉強会の問題に挑戦してみた

少し前に第10回シェル芸勉強会の問題に挑戦してみたので今回は1つ前の第9回シェル芸勉強会の問題に挑戦してみた。

Q1

Question

まず、次のようにファイルを作ってください。

$ touch apple avocado banana cinnamon melon
$ ls
apple avocado banana cinnamon melon

「a,b,c,m」というディレクトリを作って、1文字目が対応するファイルをそれぞれのディレクトリに移動してください。

###こうなったらOK###
$ ls *
a:
apple avocado
 
b:
banana
 
c:
cinnamon
 
m:
melon

Answer

cut -c1 で単語の1文字目を強引に抜き出してるけどあとで解答例を見たら ${f:0:1} みたいに Bash のパラメーター展開を使っててスマートな感じだった。

$ ls | while read f; do d=$(echo $f | cut -c1); mkdir -p $d; mv $f $d; done
$ ls *
a:
apple    avocado

b:
banana

c:
cinnamon

m:
melon

Q2

Question

まず、次のように名前にスペースが入ったファイルを作ります。

$ touch "私は 蟹" "オシャレな 蟹" "足が 10本"
$ ls -l
total 0
-rw-r--r-- 1 ueda staff 0 2 14 11:22 私は 蟹
-rw-r--r-- 1 ueda staff 0 2 14 11:22 足が 10本
-rw-r--r-- 1 ueda staff 0 2 14 11:22 オシャレな 蟹

このままでは何かと扱いづらいので、間にアンダーバーを入れて次のように名前を変更してください。

$ ls -l
total 0
-rw-r--r-- 1 ueda staff 0 2 14 11:25 私は_蟹
-rw-r--r-- 1 ueda staff 0 2 14 11:25 足が_10本
-rw-r--r-- 1 ueda staff 0 2 14 11:25 オシャレな_蟹

Answer

$ ls | while read f; do mv "$f" "$(echo $f | tr ' ' '_')"; done

Q3

Question

ディレクトリを適当に作って、20140101から20141231まで、日付に対応したファイルを作って下さい。各ファイルの中には各日付に対応するdateコマンドの出力を書き込んで下さい。

ワンライナーが思いつかない場合は、とりあえず手作業でやってみてください。)

###こんな感じでどうぞ###
uedambp:20140214USPSTUDY ueda$ ls -l | head
total 1460
-rw-r--r-- 1 ueda staff 28 2 14 10:23 20140101
-rw-r--r-- 1 ueda staff 28 2 14 10:23 20140102
-rw-r--r-- 1 ueda staff 28 2 14 10:23 20140103
-rw-r--r-- 1 ueda staff 28 2 14 10:23 20140104
-rw-r--r-- 1 ueda staff 28 2 14 10:23 20140105
-rw-r--r-- 1 ueda staff 28 2 14 10:23 20140106
-rw-r--r-- 1 ueda staff 28 2 14 10:23 20140107
-rw-r--r-- 1 ueda staff 28 2 14 10:23 20140108
-rw-r--r-- 1 ueda staff 28 2 14 10:23 20140109
uedambp:20140214USPSTUDY ueda$ cat 20140101
水 1 1 00:00:00 JST 2014

Answer

Mac の date コマンドはつらみがあるので Virtual Box 上の Ubuntu 20.04.2 LTS で解いた。

これ date コマンドで日付を相対指定するのがスマートだと思うんだけど 20140101 から 20141231 まで数値のまま繰り返し処理して強引に日付を作ってみた。

$ seq 0 $(echo '20141231-20140101' | bc) | awk '{print 20140101+$1}' | while read d; do date -d $d && date -d $d > $d; done

Q4

Question

次のように4個ファイルを作って、a_ramenとa_curry、b_appleとb_tomatoのファイルの中身を入れ替えてください。

$ echo カレー > a_ramen
$ echo ラーメン > a_curry
$ echo トマト > b_apple
$ echo リンゴ > b_tomato
###(余談)各ファイルと中身は次のようにgrepで確認できる###
$ grep "" *
a_curry:ラーメン
a_ramen:カレー
b_apple:トマト
b_tomato:リンゴ

Answer

少しちょろっと考えたけど分からなかったので解答例を見た。 解答例を分解しながら少しずつ書いてみる。

grep "" * でそれぞれのファイルとファイル内容の組み合わせを作って、

$ grep "" * 
a_curry:ラーメン
a_ramen:カレー
b_apple:トマト
b_tomato:リンゴ

tr ':' ' ': を削除して、

$ grep "" * | tr ':' ' '
a_curry ラーメン
a_ramen カレー
b_apple トマト
b_tomato リンゴ

xargs -n 4a_* のファイル同士の組み合わせを作って、

$ grep "" * | tr ':' ' ' | xargs -n 4
a_curry ラーメン a_ramen カレー
b_apple トマト b_tomato リンゴ

awk '{print $1,$4,$3,$2}' でファイルとファイル内容の組み合わせを入れ替えて、

$ grep "" * | tr ':' ' ' | xargs -n 4 | awk '{print $1,$4,$3,$2}'
a_curry カレー a_ramen ラーメン
b_apple リンゴ b_tomato トマト

awk '{print "echo",$2,">",$1}' でコマンド文字列を作って、

$ grep "" * | tr ':' ' ' | xargs -n 4 | awk '{print $1,$4,$3,$2}' |
    xargs -n 2 | awk '{print "echo",$2,">",$1}'
echo カレー > a_curry
echo ラーメン > a_ramen
echo リンゴ > b_apple
echo トマト > b_tomato

コマンド文字列を sh に流し込んで実行する。

$ grep "" * | tr ':' ' ' | xargs -n 4 | awk '{print $1,$4,$3,$2}' |
    xargs -n 2 | awk '{print "echo",$2,">",$1}' | sh
$ grep "" *
a_curry:カレー
a_ramen:ラーメン
b_apple:リンゴ
b_tomato:トマト

Q5

Question

各月ごとにtar.gzファイルにしてください。

###こんな感じで###
uedambp:20140214USPSTUDY ueda$ ls *.tar.gz
201401.tar.gz 201404.tar.gz 201407.tar.gz 201410.tar.gz
201402.tar.gz 201405.tar.gz 201408.tar.gz 201411.tar.gz
201403.tar.gz 201406.tar.gz 201409.tar.gz 201412.tar.gz

Answer

これ質問は元ネタのファイルはどこにあるのかまったく書いてないけど問題の流れから Q3 で作った日付のファイルをやってみた。

$ ls | cut -c1-6 | uniq | xargs -I{} sh -c "tar cvzf {}.tar.gz {}*"

Q6

Question

次のようなディレクトリ・ファイル操作を行って下さい。

###小問1: ディレクトリを作る###
 ~/a/a/a/.../a/a/ (aが百個)
###小問2: ファイルを作る###
 ~/a/a/a/.../a/a/b (aが百個、bはファイル)
###小問3: ~/a/a/a/.../a/a/の底に移動###
uedambp:a ueda$ pwd
/Users/ueda/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a
/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a
/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a
/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a

Answer

小問1

$ seq 100 | sed 's/.*/a/' | tr '\n' '/' | xargs mkdir -p

小問2

$ seq 100 | sed 's/.*/a/' | tr '\n' '/' | xargs -I{} touch {}b

小問3

$ find . -name a | tail -n 1 | while read f; do cd $f; done

Q7

Question

先ほど作ったファイルbを、50番目のaディレクトリに移動して下さい。

↓うまくできたかどうかの確認方法

###~から50番目のaに移動###
uedambp:~ ueda$ for a in {1..50} ; do cd a ; done
###bがあるか確認###
uedambp:a ueda$ pwd
/Users/ueda/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a
/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a
/a/a/a/a
uedambp:a ueda$ ls
a b
###さらに50個aを下る###
uedambp:a ueda$ for a in {1..50} ; do cd a ; done
uedambp:a ueda$ pwd
/Users/ueda/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a
/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a
/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a
/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a
/a/a
###なにもない###
uedambp:a ueda$ ls
uedambp:a ueda$ 

Answer

少しちょろっと考えたけど分からなかったので解答例を見た。

こういう問題を考える人って頭がいいなーって思う。

$ seq 150 | sed 's/100/&\nb ./' | sed -E 's/^[0-9][0-9]*/a/' | tr '\n' '/' | sed 's/^/mv /' | sh

$ for a in {1..50} ; do cd a ; done
$ pwd
/path/to/shell-gei-study/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a
$ ls
a/ b

$ for a in {1..50} ; do cd a ; done
$ pwd 
/path/to/shell-gei-study/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a

$ ls

Q8

Question

先ほど作ったディレクトリについて、rm -rを使わずに~/a以下のディレクトリを消去してください。

Answer

問題自体はそんなに難しくないけどディレクトリをガーッと消す操作をやるのはちょっと怖かった。

$ seq 100 | tail -r | awk '{for(i=0;i<$1;i++){printf("a/")}; print ""}' | sed 's/.*/rm -f &b; rmdir &/' | sh

参考サイト