サブシェルが閉じた後でも、サブシェル内のリダイレクトは依然として存在します。

サブシェルが閉じた後でも、サブシェル内のリダイレクトは依然として存在します。

空の状態となしの状態で実行すると、次のスクリプトはそれぞれ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つの別々の問題があります。

  1. 引用符を忘れたので、/をEOF解析すると$#hereドキュメントで拡張されます。$@
  2. ファイルが終了するまで、名前付きパイプ(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が出力を読み取るために最初の引数(上記の引数に拡張)を開く必要があることです。 。cmd2cmd2 | cmd1cmd2 | cmd1cmd2cmd1cmd1 <(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使用します。=(...)cmd2cmd1cmd1 =(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これが何を意味しているにもかかわらず、それプロセスの交換ここではないコマンドの置き換え

おすすめ記事