【困ったら】第60回シェル芸勉強会【man bash】

当日の昼に自宅からリモート参加しようとしたのですが、エアコンが壊れたため家電量販店に行かざるを得なくなり、結局深夜の空き時間ができたときに1日につき1〜2問くらいのスピードで解答することになりました。



Q1

次のようにすると、変数Aには、a1 a2 a3 a4 b1 b2 b3 b4 c1 c2 c3 c4 d1 d2 d3 d4が代入されます。

$ A=$(echo {a,b,c,d}{1,2,3,4})
$ echo $A
a1 a2 a3 a4 b1 b2 b3 b4 c1 c2 c3 c4 d1 d2 d3 d4

同様な代入を、echoを使わないで実現する方法を考えてください。{a,b,c,d}{1,2,3,4}という部分はワンライナー中に残しましょう。(できる人は複数お願いします。コマンド置換を使わない方法も考えてみましょう。)

A1

# ファイルは`while`でプロセスがforkされても関係なくグローバルに使える
$ file {a,b,c,d}{1,2,3,4} | awk '{print $1}' | sed 's/:$//' | xargs > /tmp/A1; while read L; do A=$L; done < /tmp/A1; echo $A
a1 a2 a3 a4 b1 b2 b3 b4 c1 c2 c3 c4 d1 d2 d3 d4
# `printf`を使った別解
$ printf '%s ' {a,b,c,d}{1,2,3,4} | sed 's/ $/\n/' | xargs > /tmp/A1; while read L; do A=$L; done < /tmp/A1; echo $A
a1 a2 a3 a4 b1 b2 b3 b4 c1 c2 c3 c4 d1 d2 d3 d4

Q2

1秒ごとに時刻を確認し、秒数が3の倍数のときにアホと出力するbashの関数をなるべく短く書いて実行してください。

秒数が変わる時刻ぴったりに出力する必要はありません。他の文字は出してはいけません。外部コマンドは使って構いません。

A2

# `date`の出力フォーマット指定で「-」を「%」の後に続けてフィールドの空白を埋めないようにする
$ f(){ while :; do [ $(echo $(( $(date '+%-S')%3 )) ) -eq 0 ] && echo アホ; sleep 1; done }; f
アホ
アホ
アホ
(3秒ごとに「アホ」を出力)

Q3

/を打たずに、ls /を10回繰り返す(ls / / / / / / / / / /でも可)方法をいくつか考えてください。できる人は、ls以外の外部コマンドは使わずにやってみたり、なるべく字数を少ない解答を作ってみたりしましょう。

A3

# bashの機能であるバックスラッシュエスケープシーケンスを使う
$ for i in {1..10}; do ls $'\x2F'; done
bin         dev   initrd.img      lost+found  opt   run   sys  var
boot        etc   initrd.img.old  media       proc  sbin  tmp  vmlinuz
daily_lock  home  lib             mnt         root  srv   usr  vmlinuz.old
bin         dev   initrd.img      lost+found  opt   run   sys  var
boot        etc   initrd.img.old  media       proc  sbin  tmp  vmlinuz
daily_lock  home  lib             mnt         root  srv   usr  vmlinuz.old
bin         dev   initrd.img      lost+found  opt   run   sys  var
boot        etc   initrd.img.old  media       proc  sbin  tmp  vmlinuz
daily_lock  home  lib             mnt         root  srv   usr  vmlinuz.old
(...略...)

Q4

次のdangerというファイルから、ディレクトリのファイルを消してしまうコマンドが書いてある行を抽出してください。

$ cat danger
rm "*" " " " " " "
rm " * " " " " " " "
rm " " " " " " "*"
rm " " " " * " " " "
rm " " " * " " "

A4

$ while read L; do F=$(echo "$L" | sed 's/^rm/echo/' | sh | tr -d ' *'); [ "$F" == "" ] || echo "$L"; done < danger
rm " " " " * " " " "

Q5

ls -lの出力には2つ以上続く空白が含まれます。ls以外に外部コマンドを使わずに、空白をひとつに詰めてください。

A5

# bashのパラメータ展開でパターンを置換するときに使う`//+(A)/B`は正規表現で置換する場合の`s/A+/B/g`に相当
$ S=$(ls -l); echo "${S//+( )/ }"
合計 128
drwxrwxr-x 2 root root 4096 6月 16 14:18 bin
drwxr-xr-x 3 root root 4096 6月 16 14:46 boot
-rw-r--r-- 1 root root 0 12月 24 2021 daily_lock
drwxr-xr-x 18 root root 3300 7月 4 15:15 dev
drwxr-xr-x 178 root root 12288 7月 4 15:15 etc
drwxr-xr-x 5 root root 4096 12月 24 2021 home
lrwxrwxrwx 1 root root 33 6月 16 14:19 initrd.img -> boot/initrd.img-5.10.0-15-686-pae
lrwxrwxrwx 1 root root 33 6月 16 14:19 initrd.img.old -> boot/initrd.img-5.10.0-14-686-pae
drwxr-xr-x 19 root root 4096 3月 27 14:32 lib
(...略...)

Q6

Q6小問1

次のように、ふたつの変数に入れた巨大文字を横に並べる関数を作ってください。paste等、外部コマンドは使って構いません。

Q6.jpg

Q6小問2

小問1で作った関数を、よりたくさん引数がとれるように改良してください。

A6

A6(小問1・小問2共通)

# 手持ちの環境では`figlet`の代わりに`toilet`を使わないとQ6の出力例のようにならない
$ a=$(toilet YES)
$ b=$(toilet WE)
$ c=$(toilet CAN)
$ f(){ S='paste'; for I in $(seq ${#@}); do S="$S $(echo "<(echo \"\$$I\")")"; done; eval "$S"; }; f "$a" "$b" "$c"
                                                             
m     m mmmmmm  mmmm    m     m mmmmmm     mmm    mm   mm   m
 "m m"  #      #"   "   #  #  # #        m"   "   ##   #"m  #
  "#"   #mmmmm "#mmm    " #"# # #mmmmm   #       #  #  # #m #
   #    #          "#    ## ##" #        #       #mm#  #  # #
   #    #mmmmm "mmm#"    #   #  #mmmmm    "mmm" #    # #   ##
                                                             
                                                             

小問1の解答を作っていたら小問2の解答ができましたので、これを小問1・小問2共通の解答とします。


Q7

次のような出力を得てください。前の問題はあまり関係ありません。できた人は、小問2の関数aを少し修正して再利用してみましょう。

Q7.png

A7

toilet A | sed 's/$/ /;s/[^ ]/A/g' | toilet

出力は次のようになります(画像への変換にtextimgを使用)。

A7.png


Q8

このような挙動を示す変数Aを作ってください。

$ echo $A
c
$ echo $A | grep a
c

A8

# A3と同じく、bashの機能であるバックスラッシュエスケープシーケンス(ここではバックスペース)を使う
$ A=a$'\b'c
$ echo $A
c
$ echo $A | grep a
c
Written on July 11, 2022