リダイレクトのエラーの謎4(たぶん解決)
Fri Oct 13 18:37:02 JST 2023 (modified: Fri Oct 13 19:33:14 JST 2023)
views: 2618, keywords:シェル, bash, 連載 この記事は最終更新日が1年以上前のものです。
これの解決編です。理論派ではなく感覚派なので、複雑なの苦手なのですが・・・解決しているような気がします。環境は次の通り。
echo $BASH_VERSION
$ 5.1.16(1)-release
cat /etc/lsb-release
$ DISTRIB_ID=Ubuntu
DISTRIB_RELEASE=22.04
DISTRIB_CODENAME=jammy
DISTRIB_DESCRIPTION="Ubuntu 22.04.3 LTS"
まとめてリダイレクトする記号の怪
以前の記事でも触れていますが、Bashには標準出力と標準エラー出力をまとめてリダイレクトする&>
という記号があります。また、&>
の(manによると)「望ましくない」別の書き方に、>&
があります。例を示します。
### lsの通常の出力(ファイル名)とエラーをaというファイルに格納 ###
ls /etc/hostname /etc/うんこ >& a
$ cat a
$ ls: '/etc/うんこ' にアクセスできません: そのようなファイルやディレクトリはありません
/etc/hostname
で、manに書いてないんですけど、>&
は1>&
とも書けるみたいです。2>&
だとダメです。
### これでもよい(なんでや) ###
ls /etc/hostname /etc/うんこ 1>& b
$ cat b
$ ls: '/etc/うんこ' にアクセスできません: そのようなファイルやディレクトリはありません
/etc/hostname
### これはダメ ###
ls /etc/hostname /etc/うんこ 2>& b
$ bash: b: 曖昧なリダイレクトです
1>&ファイル記述子
なのか1>&ファイル
なのか紛らわしい
で、ここで問題になるのは、Bashだと&>ファイル
と等価な1>&ファイル
と、ファイル記述子1を別のファイル記述子x
にリダイレクトする1>&x
という書き方があることです。このふたつの書き方があることで、次のようにやたらややこしいことが起こります。
### これは「3わーお」がファイル扱いされて、&>と同じ動きになる ###
ls /etc/hostname /etc/うんこ 1>&3わーお
$ cat 3わーお
$ ls: '/etc/うんこ' にアクセスできません: そのようなファイルやディレクトリはありません
/etc/hostname
### これは FD>&FD という扱いになり、FD3が開いてないので怒られる ###
ls /etc/hostname /etc/うんこ 1>&3
$ bash: 3: 不正なファイル記述子です
# 3を2にリダイレクトしておけば大丈夫
ls /etc/hostname /etc/うんこ 3>&2 1>&3
$ ls: '/etc/うんこ' にアクセスできません: そのようなファイルやディレクトリはありません
/etc/hostname
### これもダメ ###
ls /etc/hostname /etc/うんこ 1>&1000000000000000
bash: 1000000000000000: 不正なファイル記述子です
謎の解決1
で、以前の記事で謎とされていた、
ls /etc/hostname /etc/うんこ 1>&1000000000000000
$ bash: 1000000000000000: 不正なファイル記述子です #右側のFDが怒られる
ls /etc/hostname /etc/うんこ 2>&1000000000000000
$ bash: 2: 不正なファイル記述子です #左側のFDが怒られる
という挙動の違いですが、とりあえずこれは、1>&1000000000000000
のときは&>
の処理、2>&1000000000000000
のときは2>&FD
という処理が走っていると考えると合点がいきます。 (コードをまだ読んでいないし、読むの辛いので確証は得てませんが。) つまり、別のことをやっているのですから、 コード中の別の箇所がエラーを出しているということになります。 1000000000000000
がファイルでなくファイル記述子として扱われる理由はなぜかよく分からないので、 そこは疑問として残りますが、 少なくとも同じ箇所でエラーが出ているわけではなさそうです。
謎の解決2
ただ、なんで2>&1000000000000000
のときに左側のファイル記述子2のほうが叱られるか、 という問題がまだ未解決です。また、右側のファイル記述子がそこまで大きな数字でないと、 右側のほうが叱られるという現象もありました。例を再掲します。
echo 2>&1
$ #OK
echo 2>&10
$ bash: 10: 不正なファイル記述子です #10が悪い
echo 2>&10000
$ bash: 10000: 不正なファイル記述子です #10000が悪い
echo 2>&100000000000
$ bash: 2: 不正なファイル記述子です #なぜか2が悪いことになる
これについては、 次のようにstrace
を使うと分かります。 grep
を使ってリダイレクト処理をしている部分 (最初にdup2
している部分) を抽出すると、
### 開いているFD ###
strace bash -c 'echo a 2>&1' |& grep dup2 | head -n 1
$ dup2(1, 2) = 2
### 開いていないFD ###
strace bash -c 'echo a 2>&10' |& grep dup2 | head -n 1
$ dup2(10, 2) = -1 EBADF (不正なファイル記述子です)
### ulimitの値を超えるFD ###
strace bash -c 'echo a 2>&10000' |& grep dup2 | head -n 1
$ dup2(10000, 2) = -1 EBADF (不正なファイル記述子です)
### int(32bit符号付き整数)の値を超えるFD ###
strace bash -c 'echo a 2>&100000000000' |& grep dup2 | head -n 1
$ dup2(-1, 2) = -1 EBADF (不正なファイル記述子です)
となります。最後の例のように右側が intの範囲をこえた場合でもBashは(なぜか)無理にリダイレクトを実行して、 dup2
に-1
が渡って失敗します。 dup2
というシステムコールは、ふたつの引数のどっちがおかしいかを一切教えてくれないので、 Bashは第1引数が-1
でなければ第2引数、そうでなければ第1引数のところを「不正」と表示するのでしょう。 おそらく。
ということで、ほんとうならBashのコードを読めばいいものを、 ブラックボックスとして解析しているのはよいことなのかどうなのか分かりませんが、 個人的には納得しました。
現場からは以上です。やべえさん、議論にお付き合いいただきありがとうございます!細かい話が好きな人は、Software Designでやってる連載もどうぞー。