2016年10月20日

シェル芸スパルタン演習(第13回シェル芸勉強会編)

ここ最近、Unixシェルに触る機会が減っており、CLIを使う能力の低下を感じていた。
また、近日中に、第25回シェル芸勉強会の開催が予定されており、参加するにあたり予習をしたいとも考えていた。
そこで、過去に開催されたシェル芸勉強会の問題を活用し、シェル芸力をつける演習をおこなうことにした。

今回は、問題として、第13回シェル芸勉強会の設問を用いた。


実行環境

  • Arch Linux 4.8.4-1-ARCH

  • GNU bash 4.3.46

  • GNU coreutils 8.25-2

  • GNU diffutils 3.5-1

  • GNU findutils 4.6.0-2

  • util-linux 2.28.2-1

  • grep (GNU grep) 2.26

  • GNU bc 1.06.95

  • sed (GNU sed) 4.2.2

  • gawk 4.1.4


シェル芸演習

問題文は、以下のURLから。
http://blog.ueda.asia/?p=4051

模範解答は、こちらから。
http://blog.ueda.asia/?p=3792

問1

非UTFなエンコーディングのファイルから、特定の文字列を含むものを探す問題。

nkfでUTF-8に変換するだけ。
ただし、それだけだとファイル名が分からないので、nkfで変換するコマンド列を生成&実行する。

01 #!/bin/sh
02
03 ls                                   |
04 sed 's/.*/echo -n &; nkf -Lu &/'     |
05 sh                                   |
06 grep 'きく'                          |
07 awk -vFS= '$0=$1'

以下、コピペ実行用のワンライナーコード。

ls | sed 's/.*/echo -n &; nkf -Lu &/' | sh | grep 'きく' | awk -vFS= '$0=$1'

問2

ディレクトリ毎にファイル数をカウントする問題。

findコマンドでパス付きでファイルを取得し、awkの連想配列を利用して集計する。

01 #!/bin/sh
02
03 find .         |
04 tr '/' ' '     |
05 awk '{NF==3 ? arr[$2]++ : arr[$2]=0} END{for(n in arr){print n,arr[n]}}'

以下、コピペ実行用のワンライナーコード。

find . | tr '/' ' ' | awk '{NF==3 ? arr[$2]++ : arr[$2]=0} END{for(n in arr){print n,arr[n]}}'

問3

特定ディレクトリ配下にあるファイルの総数を求める問題。

これも、findでパス付きでファイル一覧を取得し、awkの連想配列を用いて集計する。

01 #!/bin/sh
02
03 find .               |
04 sed '/[^0-9]$/d'     |
05 awk -F/ '{d[$2]++} END{for(n in d){print n,d[n]}}'

以下、コピペ実行用のワンライナーコード。

find . | sed '/[^0-9]$/d' | awk -F/ '{d[$2]++} END{for(n in d){print n,d[n]}}'

問4

ファイルを、ファイル名にしたがってディレクトリに分類する問題。

全ファイルリストを出力した後、該当する曜日を生成、ディレクトリの作成とファイルの移動をおこなうコマンド列を生成する。

01 #!/bin/sh
02
03 ls                                                      |
04 xargs -I@ date -d @ '+%a %Y%m%d'                        |
05 awk '{print "mkdir",$1,"2>/dev/null ;","mv",$2,$1}'     |
06 sh

以下、コピペ実行用のワンライナーコード。

ls | xargs -I@ date -d @ '+%a %Y%m%d' | awk '{print "mkdir",$1,"2>/dev/null ;","mv",$2,$1}' | sh

問5

問4と同様に、ファイルを分類する問題。

findでファイルを列挙し、該当するディレクトリに移動するコマンド列を生成する。

01 #!/bin/sh
02
03 find . -type f                                    |
04 sed 's;.*/\([a-c]\)\(0[1-4]\); mv & \1/\1\2;'     |
05 sh

以下、コピペ実行用のワンライナーコード。

find . -type f | sed 's;.*/\([a-c]\)\(0[1-4]\); mv & \1/\1\2;' | sh

問6

同じくファイル・ディレクトリ操作の問題。

findでファイルリストを取得した後、コマンド列を生成していく。

01 #!/bin/sh
02
03 find . -type f                 |
04 tr '/' ' '                     |
05 sort -k 2,2 -k 3,3nr           |
06 awk '$2!=o2{print; o2=$2}'     |
07 tr ' ' '/'                     |
08 sed 's;.*;mv & ./;'            |
09 sh

以下、コピペ実行用のワンライナーコード。

find . -type f | tr '/' ' ' | sort -k 2,2 -k 3,3nr | awk '$2!=o2{print; o2=$2}' | tr ' ' '/' | sed 's;.*;mv & ./;' | sh

問7

タイムスタンプが最新のファイルのみを、コピーする問題。

各ディレクトリについて、ls -tを実行し、その中から先頭のものを取り出せばOK。

01 #!/bin/bash
02
03 echo {a,b,c}                     |
04 xargs ls -t                      |
05 sed -n '/:/{N; s@:\n@/@; p}'     |
06 xargs -I@ cp -p @ ./

以下、コピペ実行用のワンライナーコード。

echo {a,b,c} | xargs ls -t | sed -n '/:/{N; s@:\n@/@; p}' | xargs -I@ cp -p @ ./

問8

ファイルをローテートする問題。

単純にファイル名をずらしてリネームするだけだと、最後のファイルで失敗する。
なので、一時ファイルを作って対応する。

01 #!/bin/bash
02
03 paste <(seq 1 5) <(seq 2 5; echo 1) |
04 awk '{print $1,"mv","file"$1,"file"$1$2; print $1*10,"mv","file"$1$2,"file"$2}' |
05 sort -k 1,1n |
06 awk '{print $2,$3,$4}' |
07 sh

別バージョン。
ファイルの末尾と中身が一致している事を利用する。
ファイルをリネームするのではなく、ファイル名と中身がずれるようにして、ファイルを生成する。

01 #!/bin/sh
02
03 (seq 2 5; echo 1)                                      |
04 awk '{print "echo",$1,"> file"($1!=1 ? $1-1 : 5)}'     |
05 sh

以下、コピペ実行用のワンライナーコード。

paste <(seq 1 5) <(seq 2 5; echo 1) | awk '{print $1,"mv","file"$1,"file"$1$2; print $1*10,"mv","file"$1$2,"file"$2}' | sort -k 1,1n | awk '{print $2,$3,$4}' | sh
(seq 2 5; echo 1) | awk '{print "echo",$1,"> file"($1!=1 ? $1-1 : 5)}' | sh

雑記

  • 大きなにゃんこに抱きつきたいんじゃがー

Tags: シェル芸 Unix
このエントリーをはてなブックマークに追加