서브쉘이 닫힌 후에도 서브쉘 내의 리디렉션은 여전히 ​​존재합니다.

서브쉘이 닫힌 후에도 서브쉘 내의 리디렉션은 여전히 ​​존재합니다.

비어 있는 상태와 없는 상태로 실행하면 다음 스크립트는 각각 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

코드에는 두 가지 별도의 문제가 있습니다.

  1. 따옴표를 잊어버렸 으므로 /를 EOF구문 분석할 때 $#here 문서에서 확장됩니다 .$@
  2. 파일이 끝날 때까지 명명된 파이프(FIFO라고도 함)에서 두 번 읽으려고 합니다.

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파이프의 내용은 한 번만 읽을 수 있습니다.

In cmd1 <(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이것이 무엇을 의미하는지 에도 불구하고, 그것은프로세스 교체여기, 아니명령 대체.

답변2

다음 섹션에서는 파일 설명자 대신 소켓 파이프 스트림 설명자를 만듭니다.

cat <<EOF
...
EOF

(해당 섹션에서) 스트림을 처음 읽을 때 # dry run설명자의 읽기 포인터는 파일 끝에 도달합니다.

# live run섹션에서는 EOF(즉, string이 아닌 파일 끝 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매우 유연하며 위의 정확한 예를 따를 필요는 없습니다. 이 예는 내 사용 사례 중 하나에서 가져온 것이며 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 주위에 따옴표가 없으면 여기에서 문서를 구문 분석할 때 위치 매개변수가 여기에서 확장됩니다."

관련 정보