비어 있는 상태와 없는 상태로 실행하면 다음 스크립트는 각각 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
코드에는 두 가지 별도의 문제가 있습니다.
- 따옴표를 잊어버렸 으므로 /를
EOF
구문 분석할 때$#
here 문서에서 확장됩니다 .$@
- 파일이 끝날 때까지 명명된 파이프(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 가 출력을 읽도록 하기 위해 첫 번째 인수( 위의 인수 로 확장됨)를 열어야 한다는 것입니다. .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(즉, 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 주위에 따옴표가 없으면 여기에서 문서를 구문 분석할 때 위치 매개변수가 여기에서 확장됩니다."