배경
find
공백이 포함된 파일 이름 목록(목록을 통해)을 사용자 정의 Python 스크립트에 전달하고 싶습니다 . 따라서 find
각 결과 주위에 따옴표를 추가하도록 설정했습니다 .
find ./testdata -type f -printf "\"%p\" "
결과:
"./testdata/export (1).csv" "./testdata/export (2).csv" "./testdata/export (3).csv"
test.py
이 질문에 대답하기 위해 내 사용자 정의 스크립트( )가 다음을 수행한다고 가정해 보겠습니다 .
#!/usr/bin/python3
import sys
print(sys.argv)
관찰 결과
사례 1:
인용된 매개변수를 수동으로 나열합니다.
입력하다:./test.py "./testdata/export (1).csv" "./testdata/export (2).csv" "./testdata/export (3).csv"
산출:['./test.py', './testdata/export (1).csv', './testdata/export (2).csv', './testdata/export (3).csv']
사례 2:
사용xargs
입력하다:find ./testdata -type f -printf "\"%p\" " | xargs ./test.py
산출:['./test.py', './testdata/export (1).csv', './testdata/export (2).csv', './testdata/export (3).csv']
(즉, 출력은 다음과 같습니다.사례 1)
사례 3:
백틱을 사용하세요.
입력하다:./test.py `find ./testdata -type f -printf "\"%p\" "`
산출:['./test.py', '"./testdata/export', '(1).csv"', '"./testdata/export', '(2).csv"', '"./testdata/export', '(3).csv"']
두 가지 사항이 변경되었습니다.
"./testdata/export
이제(1).csv"
두 개의 별도 매개변수가 되었습니다.- 인용문은 여전히 논쟁의 일부입니다.
질문
백틱 버전이 다르게 동작하는 이유는 무엇입니까?
백틱과 함께 따옴표를 포함하는 방법이 있습니까? 즉,
xargs
?와 동일하게 동작하도록 만듭니다.
논평
여기서 무슨 일이 일어나고 있는지 정말 상상할 수 없습니다. 논리적인 설명은 백틱으로 표시된 명령 출력이 하나의 큰 인수로 처리된다는 것입니다. 그런데 왜 공백으로 분할됩니까?
따라서 다음으로 가장 좋은 설명은 공백으로 구분된 각 문자열이 인용 여부에 관계없이 별도의 인수로 처리된다는 것입니다. 맞습니까? 그렇다면 백틱이 왜 이렇게 이상한 동작을 하게 됩니까? 우리가 대부분의 경우 원하는 것은 그게 아닌 것 같은데...
답변1
따라서 다음으로 가장 좋은 설명은 공백으로 구분된 각 문자열이 인용 여부에 관계없이 별도의 인수로 처리된다는 것입니다. 맞습니까?
예, 예를 들어 참조하세요.https://mywiki.wooledge.org/WordSplitting그리고공백이나 기타 특수 문자 때문에 쉘 스크립트가 멈추는 이유는 무엇입니까?그리고언제 큰따옴표가 필요합니까?
쉘은 따옴표가 확장(예: 여기에서 사용하는 명령 대체 또는 매개변수 확장)의 결과가 아니라 원래 명령줄에 나타나는 경우에만 따옴표를 처리하며 따옴표 자체는 따옴표로 묶지 않습니다.
그렇다면 백틱이 왜 이렇게 이상한 동작을 하게 됩니까? 우리가 대부분의 경우 원하는 것은 그게 아닌 것 같은데...
글쎄요, 이상한 것은 상대적입니다. 한 사람이 어떤 상황에서 원하는 것이 다른 상황에서는 전혀 원하는 것이 아닐 수도 있습니다.
그러나 다음과 같은 것을 고려해보세요:
a="blah blah"
somecmd -f "$a"
작동 방식은 somecmd
변수에 포함된 문자열을 매개변수로 사용하는 것입니다 a
.그 안에 무엇이 들어있든 간에. 이는 Python과 같은 "실제" 프로그래밍 언어에서 작동하는 방식과 유사합니다 subprocess.call(["somecmd", "-f", a])
. 간단하고 깨끗하며 완전히 안전합니다. 변수에 특수 문자가 없어도 혼동을 일으킬 수 있습니다.
문자열이 스크립트 외부에서 나오거나, 파일에서 읽혀지거나, 사용자가 입력하거나, 파일 이름 확장의 결과인 경우 이는 중요합니다.
echo "Please enter a filename: "
read -r a
somecmd -f "$a"
확장 결과를 인용 처리할 경우 Don't stop me now.mp3
짝이 없는 인용이 있기 때문에 파일명을 입력할 수 없습니다.
또한 추가 확장을 위해 모든 확장 결과를 처리해야 합니까? 꽤 불쾌한 일을하도록 a
설정 하십시오 . $(rm -rf $HOME).txt
이는 완벽하게 유효한 파일 이름이므로 *.txt
.
확장 후에는 따옴표와 이스케이프만 처리하고 추가 확장은 처리하지 말라고 제안할 수 있기 때문에 이것은 약간 과장된 것입니다. 짝을 이루지 않은 작은따옴표는 여전히 문제이며 $(find -printf "\"%p\"")
큰따옴표가 포함된 파일 이름에는 여전히 작동하지 않습니다.
그런 식으로 작동할 수도 있지만 매직 핸들이 덜 조용할수록 사고가 발생할 가능성이 줄어듭니다. (껍질에 관해서는, 너무 건전해서 다행이라는 생각이 가끔 듭니다.)
find
하지만 당신 말이 맞습니다. 즉, 셸 에서 문자열 목록을 가져오는 즉각적이고 직접적인 방법이 없다는 뜻입니다 . 이것은 실제로 sys.argv
Python에서 와 마찬가지로 문자열 목록으로 원하는 것입니다 . 따옴표가 아닙니다.
다음을 수행할 수 있습니다.
find -print0 | xargs -0 ./test.py
-print0
find
파일 이름을 NUL 바이트로 구분 기호(개행 대신)로 인쇄하도록 요청 하고 그것이 우리에게 필요한 전부임을 -0
알려줍니다 . xargs
이는 파일 이름에 포함될 수 없는 유일한 바이트가 NUL 바이트이기 때문에 작동합니다. 최소한 GNU와 FreeBSD에서는 찾을 수 있습니다 -print0
.-0
또는 Bash에서:
mapfile -d '' files < <(find -print0)
./test.py "${files[@]}"
이는 프로세스 대체 및 배열에 사용되는 것과 동일한 NUL로 구분된 문자열입니다.
또는 Bash( shopt -s globstar
) 및 유사한 기능을 가진 다른 프로그램에서 파일 이름 이외의 기준으로 필터링할 필요가 없는 경우:
shopt -s globstar
./test.py ./testdata/**
**
처럼 *
그냥 재귀적이죠.
또는 표준 도구를 사용하십시오.
find -exec ./test.py {} +
find
test.py
이는 파일 이름 목록을 다른 곳으로 전달하지 않고 자체적으로 실행하도록 요청하여 전체 문제를 해결합니다 . 하지만 목록을 어딘가에 저장해야 하는 경우에는 도움이 되지 않습니다. +
마지막 것은 각 파일에 대해 한 번씩 실행된다는 점에 유의하십시오 -exec ./test.py {} \;
.test.py
답변2
xargs
입력을 특별하게 처리하십시오.
모든 개행 및 공백 시퀀스(일부 구현에서는 공백 및 탭 이상)를 구분 기호로 처리하고 선행 및 후행 시퀀스를 무시하며 고유한 특별한 방식으로 인용을 처리합니다. '...'
인용 에 사용할 수 "..."
있지만 \
동일한 구문으로 사용됩니다. way sh
( and "..."
는 '...'
큰 따옴표이지만 줄 바꿈을 포함할 수 없으며 \newline
줄 연속이 아닌 문자 그대로의 줄 바꿈입니다).
따라서 다음과 같은 입력을 위해:
"foo \ bar" 'x'\
y
xargs
두 개의 foo \ bar
합계 x<newline>y
매개변수를 생성합니다.
Split+glob 연산자는 `...`
POSIX 쉘의 목록 컨텍스트에서 인용되지 않은 명령 대체(고대 및 현대 형식 모두)를 유지합니다. $(...)
입력은 복잡한 규칙을 사용하여 문자로 분할되며 $IFS
결과 단어는 다음과 같습니다.파일 이름 생성. 견적 처리가 전혀 없습니다.
이렇게 입력하면
"a* b"
기본값(SPC, TAB, NL)을 사용하여 현재 디렉토리에서 시작하는 파일 이름 목록 $IFS
으로 추가 확장되는 단어를 생성합니다 ."a*
"a
b"
명령줄은 다음과 같습니다.
cmd "a* b"
cmd2 "x\"y"
쉘 구문의 코드입니다. 쉘의 구문에서 공백, 개행 및 따옴표도 특별한 의미를 가지며 다르게 해석됩니다 xargs
. 위의 코드는 두 개의 명령으로 구문 분석됩니다. 줄 바꿈으로 명령을 구분하고 cmd "a* b"
두 단어로 구문 분석합니다. 공백으로 단어를 구분하며 cmd
쉘 인용 연산자이므로 해당 안의 및 SPC가 특별하게 처리되지 않습니다. .etc. 잠깐만요.a* b
"..."
*
쉘과 동일한 방식으로 토큰화하기 위해 glob 한정자 zsh
가 있습니다 (zsh는 기본적으로 POSIX가 아닙니다. 이는 분할+glob이 아닌 목록 컨텍스트에서 인용되지 않은 명령 대체에 대해서만 분할을 수행하기 때문입니다). 또한 glob z
도 있습니다. Q
참조의 한 레이어를 제거하는 한정자입니다. 이 셸에서는 다음을 수행할 수 있습니다.
output_of_cmd=$(find...) # no split+glob here as we're assigning to
# scalar variable. It's not a list context
words=("${(Q@)${(z)output_of_cmd}}") # array assignment
your-app "${words[@]}"
답변3
명령 대체의 쉘 확장으로 인해 인용문이 손실되었습니다. 다시 인용하면 됩니다. $()
백틱 대신 이 형식을 사용하는 것이 좋습니다 . 코드를 더 읽기 쉽게 만듭니다.
eval ./test.py "$(find ./testdata -type f -printf "\"%p\" ")"
업데이트: 이제 다른 예제와 마찬가지로 eval을 앞에 두었습니다. 그러면 올바른 확장/인용이 발생하여 Python의 개별적으로 인용된 매개변수를 얻을 수 있습니다.