シェル芸スパルタン演習(第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
usp Personal Tukubai 20160402 ( Open usp Tukubaiで代用可能)
シェル芸演習
問題文および模範解答は、以下の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の良い練習になった。