2016年10月10日

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

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

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


実行環境

  • 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://www.slideshare.net/ryuichiueda/20130216-16574641

問1

以下の携帯電話の番号にハイフンを入れてみましょう。

$ cat tel
09011112222
08098769876
09022221111

電話番号を整形する問題。
この回はsedがメインとの事なので、sedで解答をば。

順番に区切る。

01 #!/bin/sh
02
03 cat tel                    |
04 sed 's/[0-9]\{3\}/&-/'     |
05 sed 's/[0-9]\{4\}/&-/1'

後方参照で。

01 #!/bin/sh
02
03 cat tel     |
04 sed 's/\([0-9]\{3\}\)\([0-9]\{4\}\)\([0-9]\{4\}\)/\1-\2-\3/'

awk版。コードとしては、一番分かりやすいかも。

01 #!/bin/sh
02
03 cat tel              |
04 awk -vFS= '$1=$1'    |
05 awk '{print $1$2$3"-"$4$5$6$7"-"$8$9$10$11}'

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

cat tel | sed 's/[0-9]\{3\}/&-/' | sed 's/[0-9]\{4\}/&-/1'
cat tel | sed 's/\([0-9]\{3\}\)\([0-9]\{4\}\)\([0-9]\{4\}\)/\1-\2-\3/'
cat tel | awk -vFS= '$1=$1' | awk '{print $1$2$3"-"$4$5$6$7"-"$8$9$10$11}'

問2

次の電話番号から、余計な文字を除去してハイフンを入れましょう。
(+81...は国内の番号表記に変換)

参考: http://k-tai.watch.impress.co.jp/docs/column/keyword/710475.html

$ cat tel2
0901-1112222
電話番号:08098769876
+81-90-2222-1111

問題文の通り。
sedで余計な文字を除去して、全レコードを正規化する。
その後は、問1と同じ。

01 #!/bin/sh
02
03 cat tel2                       |
04 sed 's/[^0-9+]//g'             |
05 sed '/^+/s/+[0-9]\{2\}/0/'     |
06 sed 's/\([0-9]\{3\}\)\([0-9]\{4\}\)\([0-9]\{4\}\)/\1-\2-\3/'

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

cat tel2 | sed 's/[^0-9+]//g' | sed '/^+/s/+[0-9]\{2\}/0/' | sed 's/\([0-9]\{3\}\)\([0-9]\{4\}\)\([0-9]\{4\}\)/\1-\2-\3/'

問3

以下の出力を、100行に水増ししてください。できればsedだけで。

$ cat hoge
ああああああああああああ!

sedだけという事なので、sedオンリーで。
sedはデフォルトの動作として、入力レコード(正確にはパターンスペース)を自動出力する。
そこで、あえて’p’コマンドを実行する事によって、入力レコード数を2倍に水増しできる。
なので、あとは倍々に増やしていくだけ。
100行以上に増やし終わったら、今度は自動出力を抑止するオプション"-n"を用いて、先頭100行分を出力すればOK。

01 #!/bin/sh
02
03 cat hoge     |
04 sed 'p'      |
05 sed 'p'      |
06 sed 'p'      |
07 sed 'p'      |
08 sed 'p'      |
09 sed 'p'      |
10 sed 'p'      |
11 sed 'p'      |
12 sed 'p'      |
13 sed 'p'      |
14 sed -n '1,100p'

せっかくなので、sedを使わないバージョンも。

01 #!/bin/sh
02
03 yes $(cat hoge)     |
04 head -n 100

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

cat hoge | sed 'p' | sed 'p' | sed 'p' | sed 'p' | sed 'p' | sed 'p' | sed 'p' | sed 'p' | sed 'p' | sed 'p' | sed -n '1,100p'
yes $(cat hoge) | head -n 100

問4

次の文章から、カンマ、句点を全て全角の句点に変換してください。

$ cat sakubun
働けど、働けど,我が暮らし、楽にならず,JITコンパイラ。

sedのsコマンドを使って、単純に置換するだけ。

01 #!/bin/sh
02
03 cat sakubun     |
04 sed 's/[,,、]/、/g'

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

cat sakubun | sed 's/[,,、]/、/g'

問5

以下の文字列に対して、次の変換を行ってください。
- \_を_
- _を半角空白

$ cat dadan
\ ダッダーン\_!_ボヨヨン\_ボヨヨン_

こうなれば良い
\ ダッダーン_! ボヨヨン_ボヨヨン

少し複雑な置換問題。
"_"をいきなり"_"に置換すると、""を半角空白に置換する際、誤爆してしまう。
なので、"\
"を、一旦別の文字に置き換える。
その状態で、先に""を半角空白に置換する。
最後に、別の文字に置き換えた"\
"を、"_"に置換すれば完了。

01 #!/bin/sh
02
03 cat dadan           |
04 sed 's/\\_/@/g'     |
05 sed 's/_/ /g'       |
06 sed 's/@/_/g'

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

cat dadan | sed 's/\\_/@/g' | sed 's/_/ /g' | sed 's/@/_/g'

問6

以下の文字列について、文字数を数えてください。

$ cat aiueo
あいうえお
かきくけこ

sed縛りだと、けっこう面倒臭い…​
文字列を一文字1レコードに置換し、余分な空行を除去、その後行番号を表示、最後に末尾行番号のみを表示する。

01 #!/bin/sh
02
03 cat aiueo           |
04 sed 's/./&\n/g'     |
05 sed '/^$/d'         |
06 sed -n '='          |
07 sed -n '$p'

awkバージョン。マルチバイト文字に対応しているgawkで。

01 #!/bin/sh
02
03 cat aiueo                        |
04 gawk -v OFS='\n' '$1=$1' FS=     |
05 wc -l

awk別バージョン。こちらの方が、シンプルで分かりやすいかも。

01 #!/bin/sh
02
03 cat aiueo     |
04 gawk '{len+=length($0)} END{print len}'

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

cat aiueo | sed 's/./&\n/g' | sed '/^$/d' | sed -n '=' | sed -n '$p'
cat aiueo | gawk -v OFS='\n' '$1=$1' FS= | wc -l
cat aiueo | gawk '{len+=length($0)} END{print len}'

問7

次の3つのファイルについて、シバンを全て「#!/usr/local/bin/bash」に変更してください。

$ cat a.sh
#!/bin/bash
echo hoge
$ cat b.sh
#!/bin/bash
echo hoge
$ cat c.sh
#!/bin/bash
echo hoge

sedの-iオプションを使えば、簡単に実現できる。

01 #!/bin/bash
02
03 sed -i '1c#!/usr/local/bin/bash' {a,b,c}.sh

awk版。grepの出力にファイル名が追加される事を利用して、ファイル毎に対応したコマンド列を生成、実行する。

01 #!/bin/sh
02
03 ls                                                              |
04 grep '.*.sh'                                                    |
05 awk '{print "sed 1c#!/usr/local/bin/bash",$0,">",$0".new"}'     |
06 sh

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

sed -i '1c#!/usr/local/bin/bash' {a,b,c}.sh
ls | grep '.*.sh' | awk '{print "sed 1c#!/usr/local/bin/bash",$0,">",$0".new"}' | sh

問8

次のHTMLソースのうち、table中のデータをスペース区切りで抜き出してください。

$ cat hoge.html
<table>
    <tr>
        <td>a</td><td>b</td><td>c</td>
    </tr>
    <tr>
        <td>1</td><td>2</td><td>3</td>
    </tr>
</table>

HTMLソースからの文字の抽出。
必要なレコードを抽出した後、sedのsコマンドで抜き出す。

01 #!/bin/sh
02
03 cat hoge.html     |
04 grep 'td'         |
05 awk '$1=$1'       |
06 sed 's;<[^>]*>\(.\)</[^>]*>;\1 ;g'

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

cat hoge.html | grep 'td' | awk '$1=$1' | sed 's;<[^>]*>\(.\)</[^>]*>;\1 ;g'

問9

[問題9]

aliasを全て解除してください。(bashで)

ちょっと厄介な問題。
コマンド置換を利用して、aliasされているリストを取得する。

01 #!/bin/sh
02
03 unalias $(alias |awk '$0=$2' |sed 's/=..*//')

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

unalias $(alias |awk '$0=$2' |sed 's/=..*//')

問10

次のテキストの中から、以下の部分を抽出してください。
- %%1%% と %%2%% の間
- %%3%% と %%4%% の間

$ cat coco
%%1%%
        私はだれ?
%%2%%
        ナタデココ
%%3%%
        ここはどこ?
%%4%%
        なかったでココに。
%%5%%

こういった特定範囲のレコードのみを抽出する問題は、sedが得意とするところ。
sedのアドレス指定に正規表現が使えることを利用し、更にコマンドをグルーピングすればOK。

01 #!/bin/sh
02
03 cat coco     |
04 sed -n '/%%[13]%%/,/%%[24]%%/{/%%[1234]%%/d; p}'

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

cat coco | sed -n '/%%[13]%%/,/%%[24]%%/{/%%[1234]%%/d; p}'

延長戦

以下の数列を、精度よく足してください。
(答え:3150101.7933532523523)

$ cat num
0.1243532523523
-1251.331
3151353

高精度が求められる演算なら、bcコマンドにおまかせ!

01 #!/bin/sh
02
03 cat num        |
04 xargs          |
05 tr ' ' '+'     |
06 bc

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

cat num | xargs | tr ' ' '+' | bc

雑記

  • 第03回と、まだまだ先は長いけれど…​

  • 早くも難しい問題が増えてきたかも?

  • 今回はsedがテーマということで、sedの良い練習になった。

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