くんすとの備忘録

IT系技術メモ

移転しました。

15秒後に自動的にリダイレクトします。

「第12回本当は怖くないシェル芸勉強会&第29回本が出たよUSP友の会定例会」に参加してきました

今回は関西から、Twitter参加しました。


模範解答は上記の上田さんのブログの方にあるので、ここでは自分の出した変な回答の解説などを……


Q1. 次のように、画面にバッテンを描いてください。(この出力例の大きさは21×21です。)

x                   x
 x                 x
  x               x 
   x             x  
    x           x   
     x         x    
      x       x     
       x     x      
        x   x       
         x x        
          x         
         x x        
        x   x       
       x     x      
      x       x     
     x         x    
    x           x   
   x             x  
  x               x 
 x                 x
x                   x

https://twitter.com/kunst1080/status/495441002060980224
https://twitter.com/kunst1080/status/495458118512283649


考え方は以下のようにしました

  • 空白を0、Xを1として数列を作成する
  • できあがった数列を文字列置換する


まず前半

$ (jot 9|sort -rn;echo 0;jot 9)
9
8
7
6
5
4
3
2
1
0
1
2
3
4
5
6
7
8
9

9〜0〜9の数列を作成しています。あとでまとめてパイプに渡したいため、両端をカッコでくくっています。
数列を出力するために、FreeBSDなのでjotコマンドを使用していますが、Linux系の人はseqコマンドが使えると思います。どちらもなかったらyesとawkでがんばる感じですかね。

$ (jot 9|sort -rn;echo 0;jot 9) | awk '{printf "%010d\n", 10^$1}'
1000000000
0100000000
0010000000
0001000000
0000100000
0000010000
0000001000
0000000100
0000000010
0000000001
0000000010
0000000100
0000001000
0000010000
0000100000
0001000000
0010000000
0100000000
1000000000

さっきの数列に対して、10のN乗を計算しています。また、awkのprintf関数で左ゼロ埋めをしています。
「X」の左側に見えてきましたね・・・

$ (jot 9|sort -rn;echo 0;jot 9) | awk '{printf "%010d\n", 10^$1}' | xargs -I% echo "echo -n %;echo % | rev"
echo -n 1000000000;echo 1000000000 | rev
echo -n 0100000000;echo 0100000000 | rev
echo -n 0010000000;echo 0010000000 | rev
echo -n 0001000000;echo 0001000000 | rev
echo -n 0000100000;echo 0000100000 | rev
echo -n 0000010000;echo 0000010000 | rev
echo -n 0000001000;echo 0000001000 | rev
echo -n 0000000100;echo 0000000100 | rev
echo -n 0000000010;echo 0000000010 | rev
echo -n 0000000001;echo 0000000001 | rev
echo -n 0000000010;echo 0000000010 | rev
echo -n 0000000100;echo 0000000100 | rev
echo -n 0000001000;echo 0000001000 | rev
echo -n 0000010000;echo 0000010000 | rev
echo -n 0000100000;echo 0000100000 | rev
echo -n 0001000000;echo 0001000000 | rev
echo -n 0010000000;echo 0010000000 | rev
echo -n 0100000000;echo 0100000000 | rev
echo -n 1000000000;echo 1000000000 | rev

「さっき作った文字列をrevコマンドで左右反転してくっつけるコマンド」をまず作成しています。paste使ってもよさそうですが…
echo -n は、改行せずにechoするためのオプションです。

$ (jot 9|sort -rn;echo 0;jot 9) | awk '{printf "%010d\n", 10^$1}' | xargs -I% echo "echo -n %;echo % | rev" | sh
10000000000000000001
01000000000000000010
00100000000000000100
00010000000000001000
00001000000000010000
00000100000000100000
00000010000001000000
00000001000010000000
00000000100100000000
00000000011000000000
00000000100100000000
00000001000010000000
00000010000001000000
00000100000000100000
00001000000000010000
00010000000000001000
00100000000000000100
01000000000000000010
10000000000000000001

作成したコマンドをshコマンドに食わせて実行しました。どう見てもXになっています。
あとは、1→X、0→スペース の変換をするだけです。

$ (jot 9|sort -rn;echo 0;jot 9) | awk '{printf "%010d\n", 10^$1}' | xargs -I% echo "echo -n %;echo % | rev" | sh | tr '01' ' X'
X                  X
 X                X 
  X              X  
   X            X   
    X          X    
     X        X     
      X      X      
       X    X       
        X  X        
         XX         
        X  X        
       X    X       
      X      X      
     X        X     
    X          X    
   X            X   
  X              X  
 X                X 
X                  X

……なんだか少しお題とは少し違う形をしてますが。。。

trコマンドを使って文字の置換をしました。

tr '01' ' X'

で、'0'を' 'に、'1'が'X'に、まとめて置換できます。
こんな風に、sedを使わなくてもtrコマンドでも置換ができます。


Q4. 北から順(正確には都道府県番号順)に並べてください。

$ cat pref
鹿児島県
青森県
大阪府
群馬県

https://twitter.com/kunst1080/status/495454679942168576
https://twitter.com/kunst1080/status/495455438087794688


WEBの情報を活用して作業をする、という観点の問題でした。
頭を固くせず、使えるものは使いましょうっていうことですね。

curlで取ってきた段階だと文字コードがSJISなので、nkf -w でUTF-8に変換します。

$ curl elze.tanosii.net/d/kenmei.htm -s | nkf -w
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<HTML>
<HEAD>
<META http-equiv="Content-Type" content="text/html; charset=Shift_JIS">
<META http-equiv="Content-Style-Type" content="text/css">
<TITLE>県番号,都道府県名,県庁所在地一覧</TITLE>
</HEAD>
<BODY>
(以下略

charset=Shift_JISと書いてあり、元々Shift JISだったと思われますが、nkfでUTF-8になりコンソールで見えるようになっています。

で、これにgrepしてほしいデータの行だけを抽出します。ここで

grep -of

を使うとスマートにできます。(@TSB_KZKさんに教えていただきました。)
manによると

  • -o, --only-matching…マッチした行の、マッチした部分だけを出力
  • -f FILE, --file=FILE…検索パターンをファイルから読み込む

ということです。

繋げると、

$ curl elze.tanosii.net/d/kenmei.htm -s | nkf -w | grep -of pref
青森県
群馬県
大阪府
鹿児島県


Q5. 各行の数字を大きい順にソートしてください。

$ cat input
A 31 1234 -42 4
B 10 31.1 -34 94

https://twitter.com/kunst1080/status/495463901664784384
なんぞこれ……


考え方は以下のようにしました

  • 1列目の名前で1行のデータを持つ中間ファイルを作成する
  • それぞれの中間ファイルをソートしてからくっつける


まずはこのawkの塊ですが、最後のshを外すとこうなります。

$ awk '{for(i=2;i<NF;i++){f[NR]=$1;print "echo",$i">>"$1}}END{for(i=1;i<NR+1;i++){print"(echo",f[i],";sort -n",f[i]")|xargs"}}' input
echo 31>>A
echo 1234>>A
echo -42>>A
echo 10>>B
echo 31.1>>B
echo -34>>B
(echo A ;sort -n A)|xargs
(echo B ;sort -n B)|xargs

END句も外してみて確認です。

$ awk '{for(i=2;i<NF;i++){f[NR]=$1;print "echo",$i">>"$1}}' input
echo 31>>A
echo 1234>>A
echo -42>>A
echo 10>>B
echo 31.1>>B
echo -34>>B

「2列目以降の内容を、1列目の名前のファイルに出力するコマンド」が並びます。
またこのとき、連想配列「f[]」に、「A」と「B」(1列目の値)を退避しています。これはEND句で使用します。

END句も合わせると

$ awk '{for(i=2;i<NF;i++){f[NR]=$1;print "echo",$i">>"$1}}END{for(i=1;i<NR+1;i++){print"(echo",f[i],";sort -n",f[i]")|xargs"}}' input
echo 31>>A
echo 1234>>A
echo -42>>A
echo 10>>B
echo 31.1>>B
echo -34>>B
(echo A ;sort -n A)|xargs
(echo B ;sort -n B)|xargs

となり、「2列目以降の内容を、1列目の名前のファイルに出力するコマンド」と、「1列目の名前のファイルをソートしてxargsで横にするコマンド」が出来上がります。
(sort -n は数字としてソートするオプション)

最後に、作成したコマンドをshコマンドに流しこんで実行します

$ awk '{for(i=2;i<NF;i++){f[NR]=$1;print "echo",$i">>"$1}}END{for(i=1;i<NR+1;i++){print"(echo",f[i],";sort -n",f[i]")|xargs"}}' input | sh
A -42 31 1234
B -34 10 31.1


Q7. Q6のグラフを次のように縦にしてください。(多少ズレてもよしとします。)

       * 
       * 
       * 
       * 
       * 
*      * 
*   *  * 
* * *  * 
* * *  * *
* * *  * *
5 3 4 10 2

※Q6の入力ファイル

$ cat num
5
3
4
10
2

https://twitter.com/kunst1080/status/495469327311593472
※pasteコマンドを使いたかったので、この問題はLinux(ZorinOS)で解きました。


考え方は以下のようにしました

  • 1列ずつ出力を作成する(プロセス置換(?)として作成)
  • それらをpasteで横に並べる


まず前半、xargsの手前まで

$ cat num |xargs -I% echo "<(yes \\\"\*\\\" | head -%;echo %)"
<(yes \"\*\" | head -5;echo 5)
<(yes \"\*\" | head -3;echo 3)
<(yes \"\*\" | head -4;echo 4)
<(yes \"\*\" | head -10;echo 10)
<(yes \"\*\" | head -2;echo 2)

xargsの-Iオプションで標準入力を変数化できるので、それを利用して「棒グラフ1本を生成するプロセス置換」を生成します。
試しに1行目のコマンドだけを実行してみるとこんな感じです

$ yes "*" | head -5;echo 5
*
*
*
*
*
5

さて、次にこれらをxargsで並べます。

$ cat num |xargs -I% echo "<(yes \\\"\*\\\" | head -%;echo %)" | xargs
<(yes "*" | head -5;echo 5) <(yes "*" | head -3;echo 3) <(yes "*" | head -4;echo 4) <(yes "*" | head -10;echo 10) <(yes "*" | head -2;echo 2)

並びましたね。
pasteコマンドに突っ込みたいので、echoで先頭にpasteを付けます。

$ cat num |xargs -I% echo "<(yes \\\"\*\\\" | head -%;echo %)" | xargs echo paste
paste <(yes "*" | head -5;echo 5) <(yes "*" | head -3;echo 3) <(yes "*" | head -4;echo 4) <(yes "*" | head -10;echo 10) <(yes "*" | head -2;echo 2)

これで、「『棒グラフを1本ずつ生成するプロセス置換』を横に結合するコマンド」が出来上がりました。

プロセス置換はbashの機能なので、shではなくbashに流し込みます

$ cat num |xargs -I% echo "<(yes \\\"\*\\\" | head -%;echo %)" | xargs echo paste | bash
*	*	*	*	*
*	*	*	*	*
*	*	*	*	2
*	3	*	*	
*		4	*	
5			*	
			*	
			*	
			*	
			*	
			10	

若干変な出力ですが。。。



以上です。
(なんだか今回まともにできてない気がしなくもないですが……)



今回のシェル芸会で、だんだんとpasteコマンドの使用に慣れてきたような感じです。
便利そうなので、もっと手に馴染ませていきたいところ。


次回予告

次回は、大阪で有志を募ってサテライト会場やるかも???