generated at
ゆるふわシェル問題 その4
2021/5/5にシェル芸botに突発的に投下したシェル問題集
全部で8問
そんなに難しくないはず
だいたい1~3コマンドで解ける

使う技術

問題

Q1 環境変数PATHにセットされているディレクトリの一覧を縦に並べて出力してください
a1.sh
echo $PATH | tr : \\n #シェル芸
問題の意図
環境変数 PATH を縦に並べて確認したくなることはちょいちょいあるので
解説
linuxの環境変数PATHにはディレクトリが : 区切りで設定されている
なので : を改行文字 \n に置換してあげればよい

Q2 以下のテキストファイルaの数値に3桁きざみの , (カンマ)をいれて出力してください
q2.sh
cat << EOS | tee a 1000 10000000 EOS

a2.sh
rev a | sed -E 's/.{3}/&,/g' | rev
または
a2_2.sh
cat a | numfmt --grouping
問題の意図
numfmt コマンドを知ってるかなぁ、と思い
解説
sed でやってる方
rev で一度逆順にする
前方から「任意の文字が3文字連続する」正規表現でマッチ
マッチした正規表現をそのまま使いつつ、後ろにカンマを付与して置換
rev で元に戻す
numfmt のほう
オプション渡すだけ

Q3 以下のファイル a b をカンマ区切りで横に並べて出力してください
q3.sh
seq 1 3 > a seq 3 5 > b
echo "期待値は以下"
txt
1,3 2,4 3,5

a3.sh
seq 1 3 > a seq 3 5 > b paste a b -d ,
問題の意図
paste コマンドの確認
解説
paste は引数にファイルを受け取って横に並べて出力する
横に並べる時の区切り文字を指定するときは -d オプションを使う

Q4 以下のfor文の引数のうち、「山田太郎」さん(表記のゆらぎをふくむ)にマッチするように変数を定義して、変数を呼び出すようにしてください
a4.sh
regexp="ここで定義" for word in 山田太郎 山本太郎 山田一郎 山田たろう やまだ太郎; do if [[ "\$word" =~ {ここで使用} ]]; then echo \$word fi done

a4.sh
regexp="(山田|やまだ)(太郎|たろう)" for word in 山田太郎 山本太郎 山田一郎 山田たろう やまだ太郎; do if [[ "$word" =~ $regexp ]]; then echo $word fi done

問題の意図
[[ ]] における正規表現の使い方を問う問題
結構これ間違えてしまう
解説
[[ 左 =~ 右 ]] と書く時、「右」に正規表現文字列を書くと正規表現と一致するか判定してくれる
これはbashの機能で、shにはない
この時、「左」はダブルクオートでくくっても良いが、「右」はダブルクオートでくくってはいけない
ダブルクオートも正規表現の判定に使われてしまうため
これは変数を展開する時も同じで、「左」はダブルクオートでくくっているが、「右」は変数をダブルクオートでくくってはいけない
例えば、以下は A しか出力されない
sh
[[ 山田 =~ ^山田$ ]] && echo A [[ 山田 =~ "^山田$" ]] && echo B

Q5 1111年11月11日はいまから何日前?
a5.sh
seq -f "date -d '%g years ago' +%%Y" 1000 | sh | nl | grep 111[0-9] | awk '{for (i=0; i<365; i++) { print ($1-1) * 365 + i }}' | sed -E "s/.*/date -d '& days ago' +'& %Y-%m-%d'/g" | sh | grep -F "1111-11-11"
問題の意図
date の使い方の確認
これ今まで出した問題の中で一番難しい気がする
てかゆるふわな問題では無かったかもしれない
解説
seq -f "date -d '%g years ago' +%%Y" 1000 では date コマンドの文字列を作っている
こういう文字列が返ってくる
>date -d '1 years ago' +%Y
sh にパイプで渡してコマンドを実行
実行結果に nl で連番を付与
1111年前後の年だけほしいので grep で絞り込み
awk で年から日数を生成
生成した日数の数値から sed date のコマンド文字列を生成
sh で実行
実行結果のうち、 1111-11-11 grep することで、その日付が今から何日前かがわかる
PowerShellを使うとすんなり日付の計算ができる模様
ps1
(Get-Date)-(Get-Date "1111/11/11") | Select-Object Days

Q6 bashのビルトイン関数ではないechoコマンドで寿司と出力してください
a6.sh
/bin/echo 寿司

問題の意図
なんとなく
普段使ってる echo はビルトイン(組み込み)関数なんだよということだけ
解説
単純に echo と入力するとビルトイン関数が呼ばれてしまうので、フルパスで echo を指定してあげればよい

Q7 以下のように引数を渡したときに、期待値の通り標準出力に出力できるように関数を定義してください
q7.sh
func1() { : TODO } func1 "基本設計書 (1).xlsx" "" "world"
期待値
txt
a = 基本設計書 (1).xlsx b = c = world

a7.sh
func1() { echo "a = $1" echo "b = $2" echo "c = $3" } func1 "基本設計書 (1).xlsx" "" "world"

問題の意図
関数の位置引数(positional parameter)の使い方とか
本当はダブルクオートでくくらなかったら位置がずれる、って問題にしたかったけれど問題の内容を間違えてしまった
解説
第一引数は $1 に設定される
第二、第三も同様

Q8 何らかの方法で以下の順序でechoコマンドを定義するけれど、出力順序は B A となるようにしてください。echoの前後になにかしても良いです
q8.sh
echo A echo B

a8.sh
(sleep 2; echo A) & n=$! (sleep 1; echo B) & m=$! wait $n wait $m #シェル芸

問題の意図
コマンドのバックグラウンド実行方法を問う
問題の説明の書き方が良くなかったなぁと反省
解説
コマンドの末尾に & を設定すると、そのコマンド呼び出しをバックグラウンドジョブとして実行してくれる
バックグラウンド実行されてるジョブを確認したいときは jobs と実行すると確認できる
複数のコマンドをまとめてバックグラウンドジョブに登録したいときは () でくくってサブシェルにする
サブシェルの最初に sleep を挟むことで echo が実行されるタイミングをずらすことで実行順序を入れ替えた
ただしこのままだとechoが出力される前に親のシェルプロセスが終了してしまうので wait を挟む
wait では2つのバックグラウンドジョブを待つ必要があるので、バックグラウンドジョブ登録直後にプロセスIDを変数に設定する
$! では直前のバックグラウンドジョブのプロセスIDが格納される
「直前の」なので複数バックグラウンドジョブを登録する場合は、1つバックグラウンドジョブを登録するたびに何らかの別変数に $! の値を格納しないと上書きされる
最後に wait で2つのジョブの完了を待機すれば完了

以上

余談
個人的にはQ5が一番難しいかな、と