空の状態となしの状態で実行すると、次のスクリプトはそれぞれAとBで期待どおりに実行されます。 Aのドライランに問題があります。1>/dev/null
ドライラン後も出力がまだ存在するかのように出力が抑制されます。誰かが問題を説明して解決できますか?
function baz() {
local file="$1"; shift
# dry run
local err=$(source "$file" "$@" 2>&1 1>/dev/null)
[[ -z $err ]] || { echo "Exiting"; return 1; }
# live run
local output=$(source "$file" "$@")
[[ -z $output ]] || echo "$output"
}
baz <(cat <<EOF
echo "I was given $# argument(s):" # A
# eecho "I was given $# argument(s):" # B
printf "%s " "$@"
EOF
) 'foo' 'bar'
その他:
$ uname -a
6.7.3-arch1-2 #1 SMP PREEMPT_DYNAMIC Fri, 02 Feb 2024 17:03:55 +0000 x86_64 GNU/Linux
$ bash --version
GNU bash, version 5.2.26(1)-release (x86_64-pc-linux-gnu)
答え1
コードには2つの別々の問題があります。
- 引用符を忘れたので、/を
EOF
解析すると$#
hereドキュメントで拡張されます。$@
- ファイルが終了するまで、名前付きパイプ(FIFOとも呼ばれます)から2回読み取ろうとします。
1 の場合は、以下を比較します。
$ bash --norc -s {1..42}
bash-5.3$ cat << EOF
> echo "$#"
> EOF
echo "42"
bash-5.3$ cat << 'EOF'
> echo "$#"
> EOF
echo "$#"
heredocの区切り文字を引用すると(ここには含まれていますが、'EOF'
一部のみ引用しても同じ効果があります\EOF
)、'E'OF
内部拡張は実行されません。
2の場合、より単純な再現は次のようになります。
myfunction() {
local file="$1"
stat -Lc '$file (%n) is a %F.' -- "$file"
echo First round:
cat -- "$file"
echo Second round:
cat -- "$file"
echo Done.
}
myfunction <(echo some text)
(これはGNUまたはGNUと同様の実装を想定していますstat
)。
これは作る:
bash-5.3$ myfunction <(echo some text)
$file (/dev/fd/63) is a fifo.
First round:
some text
Second round:
Done.
<(cmd)
名前付きパイプまたはhasに展開され、/dev/fd
パイプの内容は一度だけ読むことができます。
Incmd1 <(cmd2)
とinのように同時に実行されるcmd1
違いは、inの出力はfd 0から簡単に取得できるのに対して、inはfdが出力を読み取るために最初の引数(上記の引数に拡張)を開く必要があることです。 。cmd2
cmd2 | cmd1
cmd2 | cmd1
cmd2
cmd1
cmd1 <(cmd2)
cmd1
/dev/fd/63
<(cmd2)
どちらの場合も、cmd2
出力を一度読み込むと再読み込みできません。
以前のバージョンのbash(5.0以下)では、次のようにエスケープできます。
bash-5.0$ myfunction /dev/fd/3 3<< 'EOF'
> echo "$#"
> EOF
$file (/dev/fd/3) is a regular file.
First round:
echo "$#"
Second round:
echo "$#"
Done.
その時点で、ここの文書はまだ削除された状態で実行されます。 一般ファイル元の Bourne シェル実装と同じか、まだ zsh などの他のシェルにあるのと同じです。最新バージョンでは、bashはhere-docがデッドロックなしでパイプを使用できるほど小さいときにパイプを使用するように切り替えました。
bash-5.3$ myfunction /dev/fd/3 3<< 'EOF'
> echo "$#"
> EOF
$file (/dev/fd/3) is a fifo.
First round:
echo "$#"
Second round:
Done.
bash-5.3$ myfunction /dev/fd/3 3<< EOF | grep -e 9999 -e '[[:alpha:]]'
> $(seq 40000)
> EOF
$file (/dev/fd/3) is a regular file.
First round:
9999
19999
29999
39999
Second round:
9999
19999
29999
39999
Done.
ファイルを複数回読み取るには、次のものが必要です。定期的なファイルなので、オプションは次のとおりです。
zsh
たとえば、次の/dev/fd/3 3<< 'EOF'
方法に切り替えます。fifosの代わりに一時ファイルを使用し、独自のクリーンアップを処理する(そして順次実行される)プロセス置換形式に切り替えて
zsh
使用します。=(...)
cmd2
cmd1
cmd1 =(cmd2)
zsh% myfunction =(<<'EOF' cmdsubst¹ heredoc> echo "$#" cmdsubst¹ heredoc> EOF cmdsubst¹> ) $file (/tmp/zsh7xSwLQ) is a regular file. First round: echo "$#" Second round: echo "$#" Done.
mktemp
または、bashを使用する必要がある場合は、たとえばbashがあるシステム(最近のほとんど)で手動一時ファイル処理を使用します。ここで元の文書のようにあらかじめ削除しておくと、これを行うことができるので、クリーンアップについて心配する必要はありません。file=$(mktemp) || exit <<'EOF' cat > "$file" echo "$#" EOF { rm -f -- "$file" && myfunction /dev/fd/3 } 3< "$file"
この方法はLinuxまたはCygwinでのみ動作しますが、
/dev/fd/n
どこにありますか?魔法のシンボリックリンク元のファイルに。他のシステムでは、オープンはこのように/dev/fd/n
動作し、dup(n)
ファイルを開くたびにファイルの先頭に戻りません。
あるいは、あなたの場合は、ファイル/fifosの代わりにメモリで実行されるようにコードをeval
代わりに使用して渡すこともできます。source
myfunction() {
local code="$1"; shift
echo First round:
eval -- "$code"
echo Second round:
eval -- "$code"
}
myfunction "$(cat <<'EOF'
echo "I got $# argument${2+s}${1+: $@}"
EOF
)" more args
/プロセス置換ではなく、$(...)
コマンド置換(split + glob(zshでのみ分割)を防ぐために引用符が必要です)に注意してください。<(...)
=(...)
これは作る:
First round:
I got 2 arguments: more args
Second round:
I got 2 arguments: more args
気づくcmdsubst
これが何を意味しているにもかかわらず、それプロセスの交換ここではないコマンドの置き換え。
答え2
次のセクションでは、ファイル記述子の代わりにソケットパイプストリーム記述子を作成します。
cat <<EOF
...
EOF
(該当するセクションで)ストリームを最初に読み込むと、記述子の# dry run
読み取りポインタはファイルの終わりに到達します。
この# live run
セクションではEOF(つまり、文字列ではなくファイルの終わりEOF
)のみが表示されるため、このセクションでは実際には何も実行されません# live run
。
この状況で私が一般的に使用する解決策は、mktemp
次のようなものを使用することです。
TS="$(mktemp /tmp/subscript-XXXXXX.sh)"
cat >$TS <__EOF
...
__EOF
bash -x $TS fee fi fo fum
rm -f $TS
編集:OPが指摘したように、使用法はmktemp
非常に柔軟であり、上記の正確な例に従う必要はありません。この例は私のユースケースの1つから得られ、OPのシナリオに合わせて調整されていません。
答え3
関数はファイルを期待していますが、例では<(cat << EOF ...)
プロセスの置き換え時にドキュメントが渡されます。後者について述べたように、「ストリームを初めて読み込むと、記述子の読み取りポインタがファイルの末尾に到達します。」したがって、これらのファイルオブジェクトに対応するには、その内容を一時ファイルにコピーして使用することをお勧めします。
script.sh
:
function baz() {
local file="$1"; shift
temp=$(mktemp)
trap '{ rm -f "$temp"; }' EXIT ERR
cat "$file" > "$temp"
# dry run
local err=$(source "$temp" "$@" 2>&1 1>/dev/null)
[[ -z $err ]] || { echo "Exiting"; return 1; }
# live run
local output=$(source "$temp" "$@")
[[ -z $output ]] || echo "$output"
}
baz <(cat << 'EOF'
# echo "I was given $# argument(s):" # A
eecho "I was given $# argument(s):" # B
printf "%s " "$@"
EOF
) 'foo' 'bar'
Aの下:
$ source script.sh 'foo' 'bar'
I was given 2 argument(s):
foo bar
次のB:
$ source script.sh 'foo' 'bar´
Exiting
追加:
- 前に述べたように、 "[先行] EOFの周りに引用符がない場合、ここで文書を解析すると、位置パラメータがここで拡張されます。"