bourne-shell で実行行の有無効をスクリプトの外から制御する、「オプション」でない解、的な

なんつー長いタイトル…。

bourne-shell で実行行の有無効をスクリプトの外から制御する、「オプション」でない解

前置き

ワタシのサイトに「Unix シェル」そのものについてのネタが多くないのはこれは、ワタシがヘビーユーザであり「あまりに日常」だから。あえて書くようなことなんか、そうそうないんだよね。なんせこの子との付き合いは、もう25年になるんだから。

今は確かに「Unix 実機」を使うことはないし、仮想マシンですら使うのは稀なのだけれど、マインドとしては根っからの Unix ユーザなので、「Windows で Unix もどき」であるところの MSYS を「毎日」使っているわけである。

ほんとはさ、「MS DOS なんてトチ狂ったものにしがみつかないで、MSYS 使おうよ」なんてことを主張してるわけなんだから、少しは初心者向けのこの手のを書きたいと思うこともないではないんだけれど、なんせ「知らない人は過剰にビビり、そして多少は敷居が高いことは事実」なのでね、書くつもりならちゃんとしたものじゃないときっと「迷惑」なんだろうと思うんだよね。

何が言いたいのか? つまりね、今回のこのネタってさ、「シェルスクリプトの振る舞いをスクリプトの外から制御」のうちの、ごくごく限られた一つ、なのね。「シェルスクリプトの振る舞いをスクリプトの外から制御」ならそれこそ初心者が大喜びで飛びつくネタでしょ、そういう大作を…書く体力がない、すまん。

あ、一応。「bourne-shell」と言ってるけれど、拡張部分を使わない素の bourne-shell だからそう言ってるだけであって、なので当然「bash」などの bourne-shell「系」シェルには共通の話、ね。

シェルスクリプトの振る舞いを制御、のフツーのやつについて、さらっとなぞる

なぞるつーても、「スクリプトの利用者目線」のことしか言わない。

こういうことね:

1 [me@host: ~]$ ./my_script.sh -nanika=3 -doreka target.mp3

普通はこういうふうに「オプション」でコントロールさせるスクリプトを書く方が「皆が慣れてる」しわかりやすいし、実際大抵はコントロールしやすい。「何かの有効無効」ということだと、ありがちなのはもちろんこんなな:

1 [me@host: ~]$ ./configure --enable_tls

今回のをやりたくなった動機

「スクリプトでスクリプトを生成する」というようなアプローチをワタシは良く採るわけね。主に「生成後スクリプトも手で編集したい」かどうかでこのアプローチを採るかどうかを決めてる。

今回これを採用しようと思った実例がこれね。生成後スクリプトは例えばこんな:

 1 #! /bin/sh
 2 ffmpeg="c:/Program Files/ffmpeg-4.1-win64-shared/bin/ffmpeg"
 3 inmov="./#01.mp4"
 4 outbase="../#01"
 5 ext="mp4"
 6 
 7 # 00:00:00.167 -> 00:02:00.200 (120.033 secs)
 8 "${ffmpeg}" -y \
 9      -i "${inmov}" \
10      -vf "removelogo=../logo.png,fillborders=right=6:mode=fixed" \
11      -ss 00:00:00.167 -to 00:02:00.200 "${outbase}-01.${ext}"
12 
13 # 00:02:00.200 -> 00:04:44.000 (163.800 secs)
14 "${ffmpeg}" -y \
15      -ss 00:01:55.200 -i "${inmov}" \
16      -vf "removelogo=../logo.png,fillborders=right=6:mode=fixed" \
17      -ss 00:00:05.000 -to 00:02:48.800 "${outbase}-02.${ext}"
18 
19 # ...
20 
21 # 01:46:35.150 -> 01:49:34.000 (178.850 secs)
22 "${ffmpeg}" -y \
23      -ss 01:46:30.150 -i "${inmov}" \
24      -vf "removelogo=../logo.png,fillborders=right=6:mode=fixed,fade=t=out:st=182.850:d=1.000" \
25      -af "afade=t=out:st=182.850:d=1.000" \
26      -ss 00:00:05.000 -to 00:03:03.850 "${outbase}-18.${ext}"

一つの MP4 を、18 のチャプターに分割する、みたいなことね。

ただこれ、「このスクリプトを生成するスクリプト」の細かなパラメータ調整をした上でようやく完成するもの。つまり「試行錯誤で何度も生成しなおす」のね。だから「生成後スクリプト」の方を後から編集したいのだとしても、その「試行錯誤中」には出来れば手で触りたくはないわけね。

「生成後スクリプトの方を編集」したいのは、何も「微調整」だけが理由ではなくて。そう、そういうこと。「一部だけ実行したい」とか良くあるわけね。今の例だと、「前回実行してみたら -02 部分だけヘンで、直したのでそこだけやり直したい」とかね。このために、無策の場合は「該当行以外をコメントアウト」もしくは「exit する」という形で「編集」するわけだ。このタイプの編集が要するに「スクリプトがスクリプトを生成する」というアプローチと相性が悪いわけ。

ワタシは賢いので気付いてしまったのである

なわけない。25年も気付いてなかったってか。

なんにせよこれまで今回の解を使ったことはなかったんだけど、たぶんまったく同じケースにめぐり合わなかったんだろうなぁ。

この案の基礎となるのは「シェル変数」だけ。ただし、知らない人には若干呪文に見えるタイプの、だけれども非常に日常的に使う滅茶苦茶基本的なヤツ:

1 [me@host: ~]$ echo ${hoge}
2 
3 [me@host: ~]$ echo ${hoge:-"fubar"}
4 fubar
5 [me@host: ~]$ hoge=zzz...
6 [me@host: ~]$ echo ${hoge}
7 zzz...
8 [me@host: ~]$ echo ${hoge:-"fubar"}
9 zzz...

特に MSYS ユーザは「オンラインマニュアル」が使えないので辛いだろうけど、その場合は bash のマニュアルをググればこれの意味は引けるはず。いわゆる「fallback」ね。上の実例では「hoge」という変数に関して、「設定されてなければ “hubar”」としてる。

これだけ、なんだけれど、もう一つ言うべきことがあって。これね、「どこぞの DOS」とは違って、「オレが想像出来るどこででも」使えるのですよ。例えばこういうこと:

1 [me@host: ~]$ export ffmpeg="/c/Program Files/ffmpeg-4.1-win64-shared/bin/ffmpeg"
2 [me@host: ~]$ "${ffmpeg}" --help
3 ffmpeg version 4.1 Copyright (c) 2000-2018 the FFmpeg developers
4   built with gcc 8.2.1 (GCC) 20181017
5 ...

あともう一つ、「何もせずに終了コード0を返すだけのコマンド」の存在についても一応。これは「:」ね。true コマンド、でもいい、というか同じもの。なので、こういう「悩ましい」ものを使える:

1 [me@host: ~]$ ${nanika:-:} && echo "TRUE!"
2 TRUE!
3 [me@host: ~]$ ${nanika:-true} && echo "TRUE!"
4 TRUE!
5 [me@host: ~]$ nanika=false
6 [me@host: ~]$ ${nanika:-:} && echo "TRUE!"

この基礎だけを使って、こんな風にした:

「split_10mp4.sh」という名前だとして…
 1 #! /bin/sh
 2 ffmpeg="c:/Program Files/ffmpeg-4.1-win64-shared/bin/ffmpeg"
 3 inmov="./#10.mp4"
 4 outbase="../#10"
 5 ext="mp4"
 6 
 7 # 00:00:00.000 -> 00:08:59.716 (539.716 secs)
 8 "${ffmpeg001:-${ffmpeg}}" -y \
 9      -i "${inmov}" \
10      -vf "removelogo=../logo.png,fillborders=right=6:mode=fixed" \
11      -to 00:08:59.716 "${outbase}-01.${ext}"
12 ${exit001:-:}
13 
14 # 00:08:59.716 -> 00:12:36.549 (216.833 secs)
15 "${ffmpeg002:-${ffmpeg}}" -y \
16      -ss 00:08:54.716 -i "${inmov}" \
17      -vf "removelogo=../logo.png,fillborders=right=6:mode=fixed" \
18      -ss 00:00:05.000 -to 00:03:41.833 "${outbase}-02.${ext}"
19 ${exit002:-:}
20 
21 #...
22 
23 # 01:08:32.100 -> 01:20:16.000 (703.900 secs)
24 "${ffmpeg012:-${ffmpeg}}" -y \
25      -ss 01:08:27.100 -i "${inmov}" \
26      -vf "removelogo=../logo.png,fillborders=right=6:mode=fixed,fade=t=out:st=707.900:d=1.000" \
27      -af "afade=t=out:st=707.900:d=1.000" \
28      -ss 00:00:05.000 -to 00:11:48.900 "${outbase}-12.${ext}"
29 ${exit012:-:}

で、出来たこのスクリプトの制御は、これも「呪文にみえる」タイプの使い方になる:

1 [me@host: ~]$ ./split_10mp4.sh  # 全部を普通に実行
2    ...
3 [me@host: ~]$ exit002="exit 0" ./split_10mp4.sh  # -02 までのみ実行
4    ...
5 [me@host: ~]$ ffmpeg002="echo" ffmpeg003="echo"  ./split_10mp4.sh  # -02 と -03 を「嘘 ffmpeg」に
6    ...

シェル変数をセットして呼び出す、ということなので、無論こうでもいい:

1 [me@host: ~]$ export ffmpeg002="echo"
2 [me@host: ~]$ export ffmpeg003="echo"
3 [me@host: ~]$ ./split_10mp4.sh

と言いたいところだけれど、これはグローバルにいわゆる「汚染」しちゃうので、やるならサブシェルにした方がいい:

1 [me@host: ~]$ (export ffmpeg002="echo"
2 > export ffmpeg003="echo"
3 > ./split_10mp4.sh)

ご覧の通り「オプション」で制御させるのが普通は「素直」だしわかりやすいわけなんだけれど、その実現は一言で「ダルい」、シンプルに。今回のこのやり方は、使い勝手をほんの少しだけ犠牲にするけれど、作りやすいわけね。

ともあれこれによって、「生成する方のスクリプトを何度も実行」中(=絶賛試行錯誤中)に生成されたスクリプトを手で編集する必要性が格段に減る。

(これを実際に適用したものは gist に置いてある。)

2019-03-08追記: ワタシはますます賢いのでさらに気付いてしまったのである

ワタシは賢いので、なんて言うようなアホの子なので、しばらく気付かなかった。こうするとより良い:

「split_10mp4.sh」という名前だとして…
 1 #! /bin/sh
 2 ffmpeg="${ffmpeg:-c:/Program Files/ffmpeg-4.1-win64-shared/bin/ffmpeg}"
 3 inmov="./#10.mp4"
 4 outbase="../#10"
 5 ext="mp4"
 6 
 7 # 00:00:00.000 -> 00:08:59.716 (539.716 secs)
 8 "${ffmpeg001:-${ffmpeg}}" -y \
 9      -i "${inmov}" \
10      -vf "removelogo=../logo.png,fillborders=right=6:mode=fixed" \
11      -to 00:08:59.716 "${outbase}-01.${ext}"
12 ${exit001:-:}
13 
14 # 00:08:59.716 -> 00:12:36.549 (216.833 secs)
15 "${ffmpeg002:-${ffmpeg}}" -y \
16      -ss 00:08:54.716 -i "${inmov}" \
17      -vf "removelogo=../logo.png,fillborders=right=6:mode=fixed" \
18      -ss 00:00:05.000 -to 00:03:41.833 "${outbase}-02.${ext}"
19 ${exit002:-:}
20 
21 #...
22 
23 # 01:08:32.100 -> 01:20:16.000 (703.900 secs)
24 "${ffmpeg012:-${ffmpeg}}" -y \
25      -ss 01:08:27.100 -i "${inmov}" \
26      -vf "removelogo=../logo.png,fillborders=right=6:mode=fixed,fade=t=out:st=707.900:d=1.000" \
27      -af "afade=t=out:st=707.900:d=1.000" \
28      -ss 00:00:05.000 -to 00:11:48.900 "${outbase}-12.${ext}"
29 ${exit012:-:}

この場合だと:

1 [me@host: ~]$ ./split_10mp4.sh  # 全部を普通に実行
2    ...
3 [me@host: ~]$ exit002="exit 0" ./split_10mp4.sh  # -02 までのみ実行
4    ...
5 [me@host: ~]$ ffmpeg002="echo" ffmpeg003="echo" ./split_10mp4.sh  # -02 と -03 を「嘘 ffmpeg」に
6    ...
7 [me@host: ~]$ ffmpeg="echo" ffmpeg003="c:/Program Files/ffmpeg-4.1-win64-shared/bin/ffmpeg" \ 
8 > ./split_10mp4.sh  # -03 だけ「本物 ffmpeg」に
9    ...

みたいに、途中の一つだけ動かしたい場合に楽ちん。