백틱 출력에서 ​​따옴표가 다르게 처리됩니다.

백틱 출력에서 ​​따옴표가 다르게 처리됩니다.

배경

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"두 개의 별도 매개변수가 되었습니다.
  • 인용문은 여전히 ​​논쟁의 일부입니다.

질문

  1. 백틱 버전이 다르게 동작하는 이유는 무엇입니까?

  2. 백틱과 함께 따옴표를 포함하는 방법이 있습니까? 즉, 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.argvPython에서 와 마찬가지로 문자열 목록으로 원하는 것입니다 . 따옴표가 아닙니다.

다음을 수행할 수 있습니다.

find -print0 | xargs -0 ./test.py

-print0find파일 이름을 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 {} +

findtest.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*"ab"

명령줄은 다음과 같습니다.

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의 개별적으로 인용된 매개변수를 얻을 수 있습니다.

관련 정보