【問題と解答】第17回ジュンク堂はシェル芸が乗っ取った勉強会

Pocket
LINEで送る

ルール

  • ワンライナーで出されたお題を解きます。
  • 汎用的な解を考えるのは出された問題をとりあえず解いてから。
  • 特にどの環境とは指定しないので各自環境に合わせて読み替えを。ただし今回、AWKだけはGNU Awk 4.0.1を使っていると明記しておきます。
  • 今回のテーマはAWKですが、何で解いても構いません。別にPowerShellだろうがRubyだろうが構いません。ワンライナーじゃないけどエクセル方眼紙でも。

環境

今回はLinuxで解答例を作りましたので、BSDやMacな方は以下の表をご参考に・・・。

Mac,BSD系 Linux
gdate date
gsed sed
tail -r tac
gtr tr
gfold fold

Q1

次のようなデータを

$ cat data1
a 1
b 4
a 2
a 3
b 5

次のように変換してみましょう。

a 1 2 3
b 4 5

余力のある人は次のようなJSON形式にしてみましょう。

{a:[1,2,3],b:[4,5]}

解答

連想配列にデータを追記していって最後に出力するのが楽な方法です。

$ cat data1 | awk '{d[$1]=d[$1]" "$2}END{for(k in d){print k d[k]}}' 
a 1 2 3
b 4 5

JSONにするには力技(しか思い浮かばなかった)。

$ cat data1 | awk '{d[$1]=d[$1]" "$2}END{for(k in d){print k d[k]}}' |
 awk -v q='"' '{printf q$1q":[";for(i=2;i<=NF;i++){printf $i","};print "]"}' |
 xargs | tr ' ' ',' | awk '{print "{"$0"}"}' | sed 's/,]/]/g'
{a:[1,2,3],b:[4,5]}

Q2

以下の数字のファイルから同じレコード(行)があるかないかを調べ、ある場合には何行目と何行目にあるのか出力しましょう。

$ cat data
0.5937836043 0.4644710001
0.3637036697 0.5593602512
0.5655269331 0.6793148112
0.7804610574 0.2905477797
0.3637036697 0.5593602512

解答

$ cat data | awk 'a[$0]{print a[$0],NR,$0}{a[$0]=NR}'

1千万行でも10秒くらいで答えが出ることを確認済みです。もっと大きなレコード数で行う場合はもう一捻り必要です。

Q3

次のJSONのデータについて、aに対応づけられた配列内の数字の合計とbに対応づけられた配列内の数字の合計を求めましょう。

$ cat data
{"a":[1,2,3],"b":[4,5]}

解答

きれいな方法が思い浮かばないので力技で。

$ grep -o '"[ab]":\[[^\[]*\]' data | tr '":[],' '     ' |
 awk '{n=0;for(i=2;i<=NF;i++){n+=$i};print $1,n}'
a 6
b 9
$ cat data | jq . | tr -dc '[:alnum:]\n' |
 awk '/[ab]/{k=$1}!/[ab]/{n[k]+=$1}END{for(k in n){print k,n[k]}}'
a 6
b 9
###jqを使う例を。もっとうまくできるようですが・・・。###
$ cat data | jq 'reduce .a[] as $n (0; . + $n),reduce .b[] as $n (0; . + $n)'
6
9

Q4

次のようなIPv6アドレスをechoした後にパイプでコマンドをつなぎ、「::」で省略されているセクションに0を補ってください。

$ echo 2001:db8::9abc

ただし、同じワンライナーが

::1

でも使えるようにしてください。

解答

whileを使ってNFが8になるまでフィールドを補ってから処理してやると素直な処理になります。初めてシェル芸勉強会でawkのwhileを使いました・・・。

$ echo 2001:db8::9abc |
 awk -F: '{while(NF!=8){gsub(/::/,":0::",$0)};for(i=1;i<=8;i++){$i=$i!=""?$i:0};print}' |
 tr ' ' ':'
2001:db8:0:0:0:0:0:9abc
$ echo ::1 |
 awk -F: '{while(NF!=8){gsub(/::/,":0::",$0)};for(i=1;i<=8;i++){$i=$i!=""?$i:0};print}' |
 tr ' ' ':'
0:0:0:0:0:0:0:1
###別解###
$ echo 2001:db8::9abc |
 awk -F: '{while(NF!=8){gsub(/::/,":0::",$0)}print}' |
 tr ':' '\n' | awk '!NF{print 0}NF{print}' | xargs | tr ' ' ':'
Pocket
LINEで送る

【問題のみ】第17回ジュンク堂はシェル芸が乗っ取った勉強会

Pocket
LINEで送る

ルール

  • ワンライナーで出されたお題を解きます。
  • 汎用的な解を考えるのは出された問題をとりあえず解いてから。
  • 特にどの環境とは指定しないので各自環境に合わせて読み替えを。ただし今回、AWKだけはGNU Awk 4.0.1を使っていると明記しておきます。
  • 今回のテーマはAWKですが、何で解いても構いません。別にPowerShellだろうがRubyだろうが構いません。ワンライナーじゃないけどエクセル方眼紙でも。

環境

今回はLinuxで解答例を作りましたので、BSDやMacな方は以下の表をご参考に・・・。

Mac,BSD系 Linux
gdate date
gsed sed
tail -r tac
gtr tr
gfold fold

Q1

次のようなデータを

$ cat data1
a 1
b 4
a 2
a 3
b 5

次のように変換してみましょう。

a 1 2 3
b 4 5

余力のある人は次のようなJSON形式にしてみましょう。

{a:[1,2,3],b:[4,5]}

Q2

以下の数字のファイルから同じレコード(行)があるかないかを調べ、ある場合には何行目と何行目にあるのか出力しましょう。

$ cat data
0.5937836043 0.4644710001
0.3637036697 0.5593602512
0.5655269331 0.6793148112
0.7804610574 0.2905477797
0.3637036697 0.5593602512

Q3

次のJSONのデータについて、aに対応づけられた配列内の数字の合計とbに対応づけられた配列内の数字の合計を求めましょう。

$ cat data
{"a":[1,2,3],"b":[4,5]}

Q4

次のようなIPv6アドレスをechoした後にパイプでコマンドをつなぎ、「::」で省略されているセクションに0を補ってください。

$ echo 2001:db8::9abc

ただし、同じワンライナーが

::1

でも使えるようにしてください。

Pocket
LINEで送る

【問題と解答例】第13回危険でない方のシェル芸勉強会

Pocket
LINEで送る

環境

Macで解答を作ったのでLinuxな方は次のようにコマンドの読み替えを。

Mac,BSD系 Linux
gdate date
gsed sed
tail -r tac
gtr tr
gfold fold

Q1

次のように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

解答

uedambp:q1 ueda$ for f in * ; do nkf -w $f | grep -q きく && echo $f ; done
b

Q2

次のようにディレクトリa,b,c,dに1,2,…,9というファイルがあります。各ディレクトリ内のファイル数をワンライナーで数えてください。

uedambp:q2 ueda$ tree
.
├── a
│   ├── 1
│   ├── 2
│   └── 3
├── b
│   ├── 4
│   └── 5
├── c
└── d
    ├── 6
    ├── 7
    ├── 8
    └── 9

解答

uedambp:q2 ueda$ find . | tr / ' ' | awk 'NF==3{print $2}' | uniq -c
   3 a
   2 b
   4 d
uedambp:q2 ueda$ find . -type f | awk -F/ '{print $2}' | uniq -c
   3 a
   2 b
   4 d
###cも出したければ・・・###
uedambp:q2 ueda$ find . | awk -F/ '{print $2}' |
uniq -c | awk 'NF==2{print $2,$1-1}'
a 3
b 2
c 0
d 4
uedambp:q2 ueda$ ls * | xargs | gsed 's/.:/\n&/g' | awk '{print $1,NF-1}'
 -1
a: 3
b: 2
c: 0
d: 4
uedambp:q2 ueda$ ls -d * |
while read d ; do echo -n $d" " ; ls $d | gyo ; done
a 3
b 2
c 0
d 4

Q3

今度は次のような配置でファイル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

解答

uedambp:q3 ueda$ find . -type f | awk -F/ '{print $2}' | uniq -c
   5 a
   4 c

Q4

まず、次のように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

曜日別にディレクトリを作り、その中に当該するファイルを放り込んでください。

解答

uedambp:q4 ueda$ ls | gdate -f - '+%Y%m%d %a' |
while read d w ; do mkdir -p $w ; mv $d $w ; done
###英語のディレクトリにする###
uedambp:q4 ueda$ ls | LANG=C gdate -f - '+%Y%m%d %a' |
while read d w ; do mkdir -p $w ; mv $d $w ; done

Q5

以下のようにa,b,cというディレクトリを作り、その下に「{a,b,c}数字」というファイルを作ります。ファイル名の1文字目とディレクトリ名が一致するようにファイルを移動してください。

uedambp:q5 ueda$ tree
.
├── a
│   ├── a01
│   └── b01
├── b
│   ├── a02
│   ├── a03
│   └── c01
└── c
    └── a04

解答

uedambp:q5 ueda$ find . -type f |
awk '{print "mv",$1,substr($1,5,1)}'  | sh
mv: ./a/a01 and a/a01 are identical    ←エラーが出るけど大丈夫
uedambp:q5 ueda$ tree
.
├── a
│   ├── a01
│   ├── a02
│   ├── a03
│   └── a04
├── b
│   └── b01
└── c
    └── c01

Q6

次のようにディレクトリa, b, cの下に、8桁日付のファイルをいくつか置きます。

uedambp:q6 ueda$ tree
.
├── a
│   ├── 20130120
│   ├── 20140901
│   └── 20141021
├── b
│   ├── 20131011
│   └── 20140202
└── c
    ├── 20110202
    ├── 20130224
    └── 20141224

各ディレクトリの最新日付のファイルをカレントディレクトリ(a,b,cのあるディレクトリ)にコピーしてください。各ディレクトリの最新ファイルの日付はそれぞれ違い、コピーの際に衝突しないこととします。

解答

uedambp:q6 ueda$ for d in * ; do ls $d | tail -n 1 |
 xargs -n 1 -I@ cp $d/@ ./ ; done
###確認###
uedambp:q6 ueda$ ls
20140202  20141021  20141224  a  b  c
uedambp:q6 ueda$ find . -type f | tr '/' ' ' |
 awk '{f[$2]=f[$2]<$3?$3:f[$2]}END{for(k in f){print k,f[k]}}' |
 tr ' ' '/' | xargs -n 1 -I@ cp @ ./
###Tukubai等###
uedambp:q6 ueda$ find . -type f | tr '/' ' ' | sort | getlast 1 2 |
 tr '/' ' ' | awk '{print "cp", "./" $2 "/" $3 " ./"}' | sh

Q7

Q6について、適当にファイルをtouchします。今度はタイムスタンプが最新のファイルを、a, b, cそれぞれからカレントディレクトリにコピーしてください。コピーの際にタイムスタンプを変えない事。

解答

uedambp:q7 ueda$ for d in * ; do ls -t $d | head -n 1 |
 xargs -I@ -n 1 cp -p $d/@ ./ ; 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

解答

uedambp:q8 ueda$ ls | 
awk 'BEGIN{a="tmp"}{print a,$1;a=$1}END{print a,"tmp"}' | 
tail -r | awk '{print "mv",$1,$2}' | sh
uedambp:q8 ueda$ head *
==> file1 <==
5

==> file2 <==
1

==> file3 <==
2

==> file4 <==
3

==> file5 <==
4
Pocket
LINEで送る

【問題のみ】第13回危険でない方のシェル芸勉強会

Pocket
LINEで送る

環境

Macで解答を作ったのでLinuxな方は次のようにコマンドの読み替えを。

Mac,BSD系 Linux
gdate date
gsed sed
tail -r tac
gtr tr
gfold fold

Q1

次のように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

Q2

次のようにディレクトリa,b,c,dに1,2,…,9というファイルがあります。各ディレクトリ内のファイル数をワンライナーで数えてください。

uedambp:q2 ueda$ tree
.
├── a
│   ├── 1
│   ├── 2
│   └── 3
├── b
│   ├── 4
│   └── 5
├── c
└── d
    ├── 6
    ├── 7
    ├── 8
    └── 9

Q3

今度は次のような配置でファイル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

Q4

まず、次のように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

曜日別にディレクトリを作り、その中に当該するファイルを放り込んでください。

Q5

以下のようにa,b,cというディレクトリを作り、その下に「{a,b,c}数字」というファイルを作ります。ファイル名の1文字目とディレクトリ名が一致するようにファイルを移動してください。

uedambp:q5 ueda$ tree
.
├── a
│   ├── a01
│   └── b01
├── b
│   ├── a02
│   ├── a03
│   └── c01
└── c
    └── a04

Q6

次のようにディレクトリa, b, cの下に、8桁日付のファイルをいくつか置きます。

uedambp:q6 ueda$ tree
.
├── a
│   ├── 20130120
│   ├── 20140901
│   └── 20141021
├── b
│   ├── 20131011
│   └── 20140202
└── c
    ├── 20110202
    ├── 20130224
    └── 20141224

各ディレクトリの最新日付のファイルをカレントディレクトリ(a,b,cのあるディレクトリ)にコピーしてください。各ディレクトリの最新ファイルの日付はそれぞれ違い、コピーの際に衝突しないこととします。

Q7

Q6について、適当にファイルをtouchします。今度はタイムスタンプが最新のファイルを、a, b, cそれぞれからカレントディレクトリにコピーしてください。コピーの際にタイムスタンプを変えない事。

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
Pocket
LINEで送る