Bash: 서브셸에서 이스케이프된 따옴표

Bash: 서브셸에서 이스케이프된 따옴표

다음 명령을 실행할 때:

#!/bin/bash
while IFS= read -r -d '' file; do
    files+=$file
done < <(find -type f -name '*.c' -print0)
echo "${files[@]}"

나는 다음과 같은 결과를 얻지 못합니다.

#!/bin/bash
find_args="-type f '*.c' -print0"
while IFS= read -r -d '' file; do
    files+=$file
done < <(find $find_args)
echo "${files[@]}"

두 번째 장면을 첫 번째 장면과 동일하게 수정하려면 어떻게 해야 합니까?

내 이해는 큰따옴표 안에 작은따옴표가 있기 때문에 작은따옴표가 이스케이프되어 다음과 같은 잘못된 확장이 생성된다는 것입니다.

find -type f -name ''\''*.c'\'' -print0

답변1

블레어 총리의 답변정확하지만 여기서 실제로 무슨 일이 일어나고 있는지 분석하려면 (누락된 master 데이터베이스의 철자 오류를 무시하고 -name):

#!/bin/bash
while IFS= read -r -d '' file; do
    files+=$file
done < <(find -type f -name '*.c' -print0)
echo "${files[@]}"

프로세스 대체( )를 통해 시작된 쉘에서 <(...)bash는 다음 명령을 구문 분석합니다.

find -type f -name '*.c' -print0

glob이 *.c참조되기 때문에 bash는아니요확장하세요. 그러나 작은따옴표는 제거됩니다. 따라서 find프로세스가 시작되면 표시되는 매개변수 목록은 다음과 같습니다.

-type
f
-name
*.c
-print0

이러한 매개변수는 구분 기호로 구분됩니다.널 바이트, 공백이나 줄바꿈 없이. 이것은 쉘 수준이 아닌 C 수준에 있습니다. 이는 C 언어를 사용하여 execve()프로그램을 실행하는 방식과 관련이 있습니다 .

이제 이를 다음 코드 조각과 비교해 보세요.

#!/bin/bash
find_args="-type f -name '*.c' -print0"
while IFS= read -r -d '' file; do
    files+=$file
done < <(find $find_args)
echo "${files[@]}"

변수 값은 find_args다음과 같이 설정됩니다.

-type f -name '*.c' -print0

(큰따옴표는 값의 일부가 아니지만 작은따옴표 문자는예.)

명령이 find $find_args실행 되면 man bash토큰은 다음에 따라 $find_args매개변수 확장이 적용됩니다 .이어서분사이어서경로명 확장(일명 전역 확장).

매개변수 확장 후에는 -type f -name '*.c' -print0.뒤쪽에참조가 삭제되었습니다. 따라서 작은따옴표는 제거되지 않습니다.

단어 분할 후에는 다음과 같은 개별 단어를 얻게 됩니다.

-type
f
-name
'*.c'
-print0

그 다음에경로명 확장. 물론 '*.c'일반적으로 파일 이름에 작은따옴표를 넣지 않기 때문에 어떤 것과도 일치할 가능성이 없으므로 결과는 다음과 같습니다.가능한'*.c'리터럴 모드로 전달 되므로 find기본 -name데이터베이스는 모든 파일에서 실패합니다. (이름이 작은따옴표로 시작하고 세 글자로 끝나는 파일이 있는 경우에만 성공합니다 .c'.)


편집: 실제로 그러한 파일이 존재하는 경우 glob은 '*.c'해당 파일 및 기타 그러한 파일과 일치하도록 확장됩니다.확장[실제 파일 이름] find무늬. 따라서 -print0마스터 노드에 도달할지 여부는 (a)하나그러한 파일 이름 및 (b) 해당 파일 이름(glob으로 해석됨)이 일치하는지 여부.

예:

touch "'something.c'"그때 달리면전반적인 상황 '*.c'확장되면 'something.c'기본 find파일 -name 'something.c'도 일치하여 인쇄됩니다.

실행하면 touch "'namewithcharset[a].c'"glob은 '*.c'쉘에 의해 glob으로 확장되지만 find기본은-name 'namewithcharset[a].c'아니요일치 자체 - 일치만 하고 'namewithcharseta.c'존재하지 않으므로 -print0도달할 수 없습니다.

을 실행하면 touch "'x.c'" "'y.c'"glob이 '*.c'다음으로 확장됩니다.둘 다findfilename은 유효한 기본 파일 이름이 아니기 때문에 출력 오류를 일으킬 수 있습니다 'y.c'(그리고 하이픈으로 시작하지 않기 때문에 그럴 수 없습니다).


nullglob이 옵션을 설정 하면 다른 동작이 발생합니다.

또한보십시오:

답변2

-name(철자 오류가 있습니다. 두 번째 예에서는 플래그를 생략했습니다.)

한 가지 방법은 매개변수를 배열에 넣고 해당 배열을 적절하게 전달하는 것입니다 find.

#!/bin/bash
find_args=(-type f -name '*.c' -print0)
while IFS= read -r -d '' file; do
    files+=$file
done < <(find "${find_args[@]}")
echo "${files[@]}"

형식은 ${foo[@]}배열의 모든 요소로 확장되며, 각 요소는 단일 문자열로 확장되지 않고 별도의 단어입니다. 이는 원본 스크립트의 의도에 더 가깝습니다.

답변3

말한 것 외에도 다음이 필요합니다.

  • 변수는 $files기본적으로 스칼라이므로 배열로 선언하고 var+=something스칼라에 대해 문자열 연결(또는 스칼라가 할당된 경우 산술 덧셈)을 수행합니다.정수속성). 또는 var+=(something)구문을 사용하십시오(변수를 자동으로 배열로 변환합니다).
  • 변수를 초기화합니다(설정되지 않거나 빈 목록). 그렇지 않으면 환경에서 초기 값을 상속받을 수 있습니다.

행위:

files=()
while ...
  files+=$file # or files+=("$file")
done

files변수가 이전에 스크립트에서 연관 배열로 선언되지 않은 경우(이 경우 오류가 files+=something발생함 files["0"]+=something) files+=("$files")이것으로 충분합니다 .

files스크립트가 이전에 연관 배열로 정의되지 않았음 을 보장할 수 없는 경우 다음을 수행해야 할 수 있습니다.

typeset -a files=()

대신에 변수의 범위를 둘러싸는 함수로 제한하는 부작용이 있습니다. 전역 범위에서 변수를 선언하기 typeset -ga files=()때문에 문제에 대한 해결 방법으로 제대로 작동하지 않습니다 . 어떤 경우에는 변수를 설정 해제하는 대신 외부 범위(연관 배열)에서 변수를 표시하는 것이 가능하기 때문에 작동하지 않을 수도 있습니다 .bashunset files; files=()unset filesfiles

관련 정보