Bash가 명령의 출력을 인용된 문자열로 해석하도록 하려면 어떻게 해야 합니까?

Bash가 명령의 출력을 인용된 문자열로 해석하도록 하려면 어떻게 해야 합니까?

그래픽 IU(제 경우에는 macOS의 Finder)에서 파일을 선택하는 프로그램이 있습니다. 출력은 다음과 같습니다

'/tmp/file number one.txt' '/tmp/file number two.txt'

이름에 공백 문자가 있으므로 파일 이름은 '(단일 직선 체크)로 묶입니다.

bash의 명령 대체(예: ls -lcommand)에서 이 명령의 출력을 사용하면 모든 것이 엉망이 됩니다. 테스트를 위해 위 줄을 간단한 한 줄 텍스트 파일에 넣고 이를 명령줄 대체 수단으로 사용했습니다.

$ cat /tmp/files.txt
'/tmp/file number one.txt' '/tmp/file number two.txt'
$ ls -l $(</tmp/files.txt)
ls: "'/tmp/file: No such file or directory
ls: '/tmp/file: No such file or directory
ls: number: No such file or directory
ls: number: No such file or directory
ls: one.txt': No such file or directory
ls: two.txt'": No such file or directory

파일 이름 문자열을 변수에 할당하고 사용할 때도 같은 일이 발생합니다.

$ xxx="'/tmp/file number one.txt' '/tmp/file number two.txt'"
$ ls -l $xxx
ls: '/tmp/file: No such file or directory
ls: '/tmp/file: No such file or directory
ls: number: No such file or directory
ls: number: No such file or directory
ls: one.txt': No such file or directory
ls: two.txt': No such file or directory

이 문제를 해결하는 방법을 아시나요? 이스케이프된 파일 이름을 명령줄에 직접 복사하면 예상대로 작동합니다.

$ ls -l '/tmp/file number one.txt' '/tmp/file number two.txt'
-rw-r--r--  1 tester  wheel     0B Jul 17 17:21:11 2021 /tmp/file number one.txt
-rw-r--r--  1 tester  wheel     0B Jul 17 17:21:16 2021 /tmp/file number two.txt

나의 궁극적인 목표는 현재 Finder 선택 항목(컴파일된 Applescript를 통해 얻은)을 bash에서 사용할 수 있도록 하는 것입니다. 예를 들어, ls파일 목록 이나 기타 파일 처리 콘텐츠를 사용할 수 있습니다.tarcpmv

답변1

전환이 옵션인 경우 zsh이를 위해 설계된 z및 매개변수 확장 플래그를 사용할 수 있습니다.Q

file_content=$(</tmp/files.txt)
quoted_strings=(${(z)file_content})
strings_with_one_layer_of_quotes_removed=("${(Q@)quoted_strings}")
ls -ld -- "$strings_with_one_layer_of_quotes_removed[@]"

아니면 한 번에 모든 작업을 수행하세요.

ls -ld -- "${(Q@)${(z)$(</tmp/files.txt)}}"

파일에서 참조되는 구문이 와 동일하다고 가정합니다 zsh.

Z구문 분석 수행 방법을 조정하려면 매개변수 확장 도 참조하세요 . 예를 들어 파일에 #무시해야 하는 주석(with)이 포함되어 있고 두 줄 이상이면 다음을 수행해야 합니다.

ls -ld -- "${(Q@)${(Z[Cn])$(</tmp/files.txt)}}"

info zsh flags자세히보다.


zsh¹ 이제 최신 버전의 macOS에서는 기본 대화형 셸이라고 들었습니다 .

답변2

문자 그대로 작은따옴표가 포함된 다음 문자열이 있다고 가정해 보겠습니다.

'/tmp/file number one.txt' '/tmp/file number two.txt'

명령줄의 일부로 인라인으로 제공하면 제대로 작동하지만 확장에서 제공할 때는 제대로 작동하지 않는다는 것을 알 수 있습니다. 변수 확장인지 명령 대체인지는 중요하지 않으며 규칙은 둘 다 동일합니다. 따옴표가 없는 확장은 단어 분할을 거치는데 공백에 대한 분할은 /tmp/filenumber사이에서 분할 되므로 여기서는 수행하고 싶지 않습니다 . 따옴표 붙은 확장은 분할을 수행하지 않지만 두 개의 중간 작은따옴표 사이를 분할하고 싶을 수도 있으므로 그렇게 하고 싶지도 않습니다. 게다가 또 다른 사실이 있는데,확장으로 생성된 인용문은 아무 것도 인용하지 않습니다.. 그래서 우리는 뭔가 다른 일을 해야 합니다.

출력이 쉘 구문으로 알려져 있고 안전하다고 가정하면 eval쉘이 다음을 사용하여 따옴표를 해석하기 위해 또 다른 처리 라운드를 수행하도록 할 수 있습니다.

#!/bin/bash
input="'/tmp/file number one.txt' '/tmp/file number two.txt'"
eval "ls -ld -- $input"

또는 나중에 사용할 수 있도록 배열에 넣습니다.

#!/bin/bash
input="'/tmp/file number one.txt' '/tmp/file number two.txt'"
eval "files=($input)"
for f in "${files[@]}"; do
    printf "<%s>\n" "$f"
done

실행될 문자열에 eval따옴표나 큰따옴표 없이 명령 대체가 포함되어 있는 경우(예를 들어 는 /dir/$(uname -a), 는 아님 '/dir/$(uname -a)'), 쉘은~ 할 것이다처리 실행과 관련된 명령입니다 eval. 마찬가지로 문자열에 따옴표가 없는 문자열이 포함되어 있으면 )배열 할당이 종료됩니다. 따라서 자신이 통제할 수 있는 소스에만 사용하는 것이 가장 좋습니다.

또한 따옴표를 처리하기 전에 문자열을 분할하고 와일드카드로 처리하는 것을 eval원하지 않으므로 문자열 주위에 큰따옴표를 사용해야 합니다 .eval


xargs따옴표를 해석하지만 기본적으로 따옴표 붙은 문자열을 사용하는 등 확장을 처리하지 않는 다른 도구를 사용하는 방법이 있을 수 있습니다 . 예를 들어, 다음은 printf각 파일 이름에 대해 별도의 인수로 1을 실행합니다.

printf '%s\n' "$input" | xargs printf ":%s:\n"

또는 ls다음을 실행하세요.

printf '%s\n' "$input" | xargs ls -ld --

또는 xargs더 간단한 형식으로 파일 이름을 인쇄한 다음 셸의 배열에 로드할 수 있는 프로그램을 실행할 수도 있습니다. (이것은 약간 거꾸로 된 것이지만 Bash가 확장이 아닌 참조 처리만 수행하도록 하는 방법을 모르겠습니다.)

#!/bin/bash
readarray -td '' files < <(
  printf '%s\n' "$input" | xargs printf "%s\0")
for f in "${files[@]}"; do
    printf "<%s>\n" "$f"
done

(여기서 printfNUL 바이트로 끝나는 문자열이 출력되는데 readarray -td ''²는 이 형식의 출력을 기대합니다. NUL은 파일 이름에 나타날 수 없는 유일한 값이며 이는 모호하지 않고 비교적 간단한 형식입니다.)

그러나 이는 xargs쉘과 다르게 정확한 인용 규칙을 이해한다는 점에 유의하십시오. $'...'Bash가 포함된 개행 문자가 포함된 값을 출력하기 위해 경우에 따라 사용하는 인용 스타일을 모르고 큰따옴표 안의 백슬래시를 인식하지 못합니다. 4 ... 하지만 Finder의 출력이 작은따옴표일 경우(및 백슬래시를 어려운 작은따옴표를 인용하면 됩니다. 아마 괜찮을 것입니다.


1 쉘의 내장이 printf아닌 독립 실행형 유틸리티, 빈 입력에서도 적어도 한 번(일부 BSD 제외), 목록이 큰 경우 여러 번 가능printf

² bash 4.4 이상이 필요합니다.

³ 1990년대 ksh93에 의해 도입됨

1970년대 후반 PWB Unix에 PWB 4.4가 등장했을 때xargs 인용 구문은 Bourne 이전 버전(Mashey Shell)의 구문과 유사했지만 shBourne 쉘은 아니었고 ksh93이나 bash는 말할 것도 없습니다.

답변3

최선의 선택은NUL로 구분된 출력이 생성되도록 쓸모 없는 파일 목록을 생성하는 모든 항목을 수정합니다.(왜냐하면 NUL은오직경로/파일 이름에 나타날 수 없는 문자이며 유효한 문자가 포함된 모든 파일 이름을 처리하도록 보장되는 유일한 구분 기호입니다. 이것이 가능하지 않은 경우 NUL 구분 형식으로 변환하여 "수정"을 함께 엮을 수 있습니다.

다음 Perl 한 줄짜리 코드는 (대부분) 파일을 따옴표 없이 NUL로 구분된 파일 이름으로 변환합니다.

perl -0 -pe "s/'\s+'/\0/sg; s/^'|'\$//sg; s/\x0d?\x0a\$//" file.txt

첫 번째 정규식은 시퀀스를 NUL 문자로 바꿉니다 single-quote, one-or-more whitespace chars, single-quote(쉼표와 공백은 패턴의 일부가 아니며 단지 영어 목록 구분 기호일 뿐입니다). 두 번째 정규식은 입력의 시작과 끝 부분에 있는 따옴표를 제거하고, 세 번째 정규식은 "줄"의 끝 부분에서 LF 또는 CRLF를 제거합니다.

이것은아직 완벽함과는 거리가 멀다- 파일 이름에 작은따옴표나 LF를 포함해야 하는지 100% 확신할 수 없기 때문에 일부 입력은 수정할 수 없습니다(그래서 NUL로 구분된 파일로 시작하는 것이 올바른 솔루션입니다. 나중에 합치려고 하지 마세요).

예를 들어, 파일 이름의 시작이나 끝에 작은따옴표가 포함된 파일 이름이 있거나, 작은따옴표가 포함되어 있고 그 뒤에 하나 이상의 공백 문자가 있고 그 뒤에 또 다른 작은따옴표가 있는 경우 실패합니다(예: ) - 첫 번째 정규식의 전역 수정자(첫 번째 정규식뿐만 아니라 입력의 모든 파일 이름과 일치해야 함) ' '때문에 이들 모두는 NUL로 대체됩니다 . /g제가 아직 생각하지 못한 또 다른 코너 케이스가 있을 수도 있습니다.

출력을 다른 파일로 리디렉션하거나, 에 공급 xargs -0r하거나, readarraybash 내장 및 프로세스 대체와 함께 사용하여 배열을 채울 수 있습니다.

readarray -d '' files < <(perl -0 -pe "s/'\s+'/\0/sg;
                                       s/^'|'\$//sg;
                                       s/\x0d?\x0a\$//" file.txt)

xxd출력을 ( hd또는 유사한 16진수 덤프 프로그램) 로 파이프하면 hexdumpNUL로 구분된 것을 볼 수 있습니다.

00000000: 2f74 6d70 2f66 696c 6520 6e75 6d62 6572  /tmp/file number
00000010: 206f 6e65 2e74 7874 002f 746d 702f 6669   one.txt./tmp/fi
00000020: 6c65 206e 756d 6265 7220 7477 6f2e 7478  le number two.tx
00000030: 74                                       t

관련 정보