공백이있는 생성 된 파일 이름 목록을 인수 목록으로 사용 방법에 대한 규칙이지만 어떻게 해결해야합니까? 이것은 당혹

에 의해 수집 된 파일 이름 목록이있는 스크립트를 호출하려고합니다 find. 다음과 같은 특별한 내용은 없습니다.

$ myscript `find . -name something.txt`

문제는 일부 경로 이름에 공백이 포함되어 있으므로 인수 확장시 두 개의 유효하지 않은 이름으로 나뉩니다. 일반적으로 이름을 따옴표로 묶을 것이지만 여기에 인용 부호 확장으로 삽입됩니다. find각 파일 이름 의 출력을 필터링하고 따옴표로 묶으 려고 시도했지만 bash가 볼 때 시간이 너무 늦어서 파일 이름의 일부로 처리됩니다.

$ myscript `find . -name something.txt | sed 's/.*/"&"/'`
No such file or directory: '"./somedir/something.txt"'

예, 이것이 명령 행 처리 방법에 대한 규칙이지만 어떻게 해결해야합니까?

이것은 당혹 스럽지만 올바른 접근 방식을 찾지 못했습니다. 나는 마침내 그것을 어떻게 해야하는지 알아 냈습니다 xargs -0 -n 10000…하지만 여전히 묻고 싶은 추악한 해킹입니다 : 어떻게 역 인용 확장의 결과를 인용하거나 다른 방식으로 동일한 효과를 얻습니까?

편집 : 나는 사실에 대해 혼란스러워 xargs 하지 가 지시 한 경우 나 시스템의 한계를 초과 할 수없는 한, 하나의 인수 목록으로 수집 모든 인수를. 나를 똑바로 설정해 주신 모든 분들께 감사드립니다! 다른 사람들은 받아 들여진 대답을 읽을 때 직접 지적하지 않기 때문에 이것을 명심하십시오.

나는 대답을 받아 들였지만 내 질문은 여전히 ​​남아있다 : ​​백틱 (또는 $(...)) 확장 에서 공백을 보호 할 방법이 없습니까? (허용 된 솔루션은 bash가 아닌 답변입니다.)



답변

당신은 몇 가지 구현을 사용하여 다음을 수행 할 수 findxargs같은합니다.

$ find . -type f -print0 | xargs -r0 ./myscript

또는 일반적으로 단지 find:

$ find . -type f -exec ./myscript {} +

다음 샘플 디렉토리가 있다고 가정하십시오.

$ tree
.
|-- dir1
|   `-- a\ file1.txt
|-- dir2
|   `-- a\ file2.txt
|-- dir3
|   `-- a\ file3.txt
`-- myscript

3 directories, 4 files

이제 내가 이것을 가지고 있다고 가정 해 봅시다 ./myscript.

#!/bin/bash

for i in "$@"; do
    echo "file: $i"
done

이제 다음 명령을 실행할 때

$ find . -type f -print0 | xargs -r0 ./myscript
file: ./dir2/a file2.txt
file: ./dir3/a file3.txt
file: ./dir1/a file1.txt
file: ./myscript

또는 두 번째 양식을 다음과 같이 사용할 때 :

$ find . -type f -exec ./myscript {} +
file: ./dir2/a file2.txt
file: ./dir3/a file3.txt
file: ./dir1/a file1.txt
file: ./myscript

세부

찾기 + xargs

위의 두 가지 방법은 다르게 보이지만 본질적으로 동일합니다. 첫 번째는 find에서 출력을 가져 와서 스위치를 \0통해 NULL ( )을 사용하여 결과를 분할하는 -print0것입니다. 는 xargs -0특히의이 널 (null)을 사용하여 분할 것을 입력을하도록 설계되었습니다. 그 표준이 아닌 구문은 GNU에 의해 도입 find하고 xargs또한 가장 최근의 BSD의 같은 몇 가지 다른에서 현재 발견된다. -r옵션은 호출하지 않도록 할 필요가 myscript있는 경우 findGNU와 발견 아무것도 find아닌 BSD의과를.

참고 : 이 전체 접근 방식은 너무 긴 문자열을 전달하지 않는다는 사실에 달려 있습니다. 그렇다면, 두 번째 호출은 ./myscript찾기에서 나머지 후속 결과와 함께 시작됩니다.

+로 찾기

이것이 표준 방법입니다 (GNU 구현에 비교적 최근에 추가되었지만 (2005) find). 우리가하고있는 일을하는 능력 xargs은 말 그대로에 내장되어 find있습니다. 따라서 find파일 목록을 찾은 다음 필요한 경우 명령을 여러 번 실행하여 이후에 지정된 명령 -exec( 이 경우 {}바로 전에 만 지속될 수 있음)에 맞는 수의 인수를 해당 목록에 전달 +합니다.

왜 인용이 없습니까?

첫 번째 예에서는 인수를 분리하기 위해 NULL을 사용하여 인용 문제를 완전히 피함으로써 바로 가기를 사용합니다. 때 xargs이 목록을 주어 그것을 효과적으로 우리의 개인적인 명령 원자를 보호 NULL을에 분할에 지시합니다.

두 번째 예에서는 결과를 내부에 유지 find하므로 각 파일 아톰이 무엇인지 알 수 있으며 결과를 적절하게 처리 할 수 ​​있으므로 후견인의 인용 비즈니스를 피할 수 있습니다.

명령 행의 최대 크기?

이 질문은 때때로 나오므로 보너스 로이 답변에 추가하고 있습니다. 주로 나중에 찾을 수 있습니다. 다음 xargs과 같이 환경 한계를 확인하는 데 사용할 수 있습니다 .

$ xargs --show-limits
Your environment variables take up 4791 bytes
POSIX upper limit on argument length (this system): 2090313
POSIX smallest allowable upper limit on argument length (all systems): 4096
Maximum length of command we could actually use: 2085522
Size of command buffer we are actually using: 131072


답변

find . -name something.txt -exec myscript {} +

위에서 find일치하는 모든 파일 이름을 찾고에 대한 인수로 제공합니다 myscript. 공백이나 다른 홀수 문자에 관계없이 파일 이름으로 작동합니다.

모든 파일 이름이 한 줄에 들어가면 myscript가 한 번 실행됩니다. 쉘이 처리하기에 목록이 너무 길면 find는 필요에 따라 myscript를 여러 번 실행합니다.

추가 : 명령 줄에 몇 개의 파일이 들어 갑니까? “xargs가 빌드하는 것과 같은 방식으로”명령 행 man findfind빌드 한다고합니다 . 그리고 man xargs한계는 시스템에 따라 다르며를 실행하여 한계를 결정할 수 있습니다 xargs --show-limits. ( getconf ARG_MAX또한 가능성이 있습니다). Linux에서 제한은 일반적으로 명령 줄당 약 2 백만 자입니다 ( 항상 그런 것은 아님) .


답변

@ slm의 정답에 추가되었습니다.

인수 크기에 대한 제한은 execve(2)시스템 호출에 있습니다 (실제로 인수 및 환경 문자열 및 포인터의 누적 크기에 따라 다름). myscript쉘이 해석 할 수있는 언어로 작성된 경우 ,이를 실행할 필요가없는 경우, 다른 인터프리터를 실행하지 않고 쉘이 해석하도록 할 수 있습니다.

스크립트를 다음과 같이 실행하는 경우 :

(. myscript x y)

그것은 다음과 같습니다

myscript x y

그것이 현재 쉘의 자식에 의해 해석되는 것을 제외하고는 그것을 실행 하는 대신 (더 많은 인수로 실행 sh (또는 she-bang 라인이 지정하는 경우)).

이제 분명히 셸의 내장 명령이므로 명령 find -exec {} +과 함께 사용할 수 없으므로 셸이 아닌 셸에서 실행해야합니다 ...find

를 사용하면 zsh쉽습니다.

IFS=$'\0'
(. myscript $(find ... -print0))

또는:

(. myscript ${(ps:\0:)"$(find ... -print0)"}

를 사용하더라도 대부분의 기능이 글 로빙에 내장되어 있으므로 처음 zsh에는 필요하지 않습니다 .findzsh

bash그러나 변수는 NUL 문자를 포함 할 수 없으므로 다른 방법을 찾아야합니다. 한 가지 방법은 다음과 같습니다.

files=()
while IFS= read -rd '' -u3 file; do
  files+=("$file")
done 3< <(find ... -print0)
(. myscript "${files[@]}")

4.0 이상 globstar에서 옵션 과 함께 zsh 스타일 재귀 globbing을 사용할 수도 있습니다 bash.

shopt -s globstar failglob dotglob
(. myscript ./**/something.txt)

4.3 **에서 수정 될 때까지 디렉토리에 대한 심볼릭 링크 가 이어졌습니다 bash. 또한 globbing 한정자를 bash구현하지 않으므로 zsh모든 기능을 사용할 수는 없습니다 find.

또 다른 대안은 GNU를 사용하는 것입니다 ls.

eval "files=(find ... -exec ls -d --quoting-style=shell-always {} +)"
(. myscript "${files[@]}")

당신이 있는지 확인하려면 위의 방법도 사용할 수 myscript있다 실행 (인수 목록이 너무 큰 경우 실패) 한 번만. 최신 Linux 버전에서는 다음을 사용하여 인수 목록에 대한 제한을 높이거나 높일 수 있습니다.

ulimit -s 1048576

(1GiB 스택 크기, 1/4은 arg + env 목록에 사용될 수 있습니다).

ulimit -s unlimited

(제한 없음)


답변

대부분의 시스템에서 사용하는 모든 프로그램에 전달되는 명령 줄의 길이에 제한이 xargs또는 -exec command {} +. 보낸 사람 man find:

-exec command {} +
      This  variant  of the -exec action runs the specified command on
      the selected files, but the command line is built  by  appending
      each  selected file name at the end; the total number of invoca
      tions of the command will  be  much  less  than  the  number  of
      matched  files.   The command line is built in much the same way
      that xargs builds its command lines.  Only one instance of  `{}'
      is  allowed  within the command.  The command is executed in the
      starting directory.

호출은 훨씬 적지 만 하나가 될 수는 없습니다. 당신이해야 할 일은 명령 줄 인수를 기반으로 가능한 stdin에서 스크립트에서 NUL로 구분 된 파일 이름을 읽는 것입니다 -o -. 나는 다음과 같은 것을 할 것이다 :

$ find . -name something.txt -print0 | myscript -0 -o -

옵션 인수를 myscript적절하게 구현하십시오 .


답변

백틱 (또는 $ (…)) 확장에서 공백을 보호 할 방법이 없습니까?

아닙니다. 왜 그런 겁니까?

Bash는 무엇을 보호해야하고 무엇을 보호해야하는지 알 방법이 없습니다.

유닉스 파일 / 파이프에는 배열이 없습니다. 바이트 스트림 일뿐입니다. ``또는 내부의 명령 $()은 스트림을 출력합니다. 스트림은 bash가 삼키고 단일 문자열로 취급합니다. 이 시점에서 따옴표로 묶거나 하나의 문자열로 유지하거나 알몸으로 두어 선택하면 bash가 구성된 동작에 따라 분할됩니다.

따라서 배열을 원한다면 배열이있는 바이트 형식을 정의하고 도구가 좋아 xargs하고 find수행하는 것입니다. -0인수를 사용하여 실행하면 요소를 종료하는 이진 배열 형식에 따라 작동합니다 그렇지 않으면 불투명 바이트 스트림에 의미를 추가하는 null 바이트

불행하게도, bash널 바이트에서 문자열을 분할하도록 구성 할 수 없습니다. 이를 보여 주신 /unix//a/110108/17980 에게 감사드립니다 zsh.

xargs

명령을 한 번만 실행하면 xargs -0 -n 10000문제 가 해결 되었다고합니다 . 그러나 10000 개가 넘는 매개 변수가있는 경우 명령이 두 번 이상 실행됩니다.

엄격하게 한 번 실행하거나 실패하게하려면 -x인수와 -n인수보다 큰 인수 를 제공해야합니다 -s(실제로 : 길이가 0 인 인수와 명령 이름이 일치하지 않을 정도로 커야 함) -s크기). ( man xargs , 아래 발췌 부분 참조)

현재 사용중인 시스템의 스택은 약 8M로 제한되어 있으므로 내 한계는 다음과 같습니다.

$ printf '%s\0' -- {1..1302582} | xargs -x0n 2076858 -s 2076858 /bin/true
xargs: argument list too long
$ printf '%s\0' -- {1..1302581} | xargs -x0n 2076858 -s 2076858 /bin/true
(no output)

세게 때리다

외부 명령을 사용하지 않으려면 /unix//a/110108/17980에 표시된 것처럼 배열을 공급하는 while-read 루프 는 bash가 항목을 분할하는 유일한 방법입니다 null 바이트

( . ... "$@" )스택 크기 제한을 피하기 위해 스크립트를 소싱하는 아이디어 는 훌륭하지만 (시도했습니다!) 정상적인 상황에서는 중요하지 않습니다.

stdin에서 다른 것을 읽으려면 프로세스 파이프에 특수 fd를 사용하는 것이 중요하지만 그렇지 않으면 필요하지 않습니다.

따라서 일상적인 가정 요구에 가장 간단한 “기본”방법 :

files=()
while IFS= read -rd '' file; do
    files+=("$file")
done <(find ... -print0)

myscriptornonscript "${files[@]}"

프로세스 트리가 깨끗하고보기 좋으면이 메소드를 사용하면 exec mynonscript "${files[@]}"bash 프로세스를 메모리에서 제거하고 호출 된 명령으로 대체 할 수 있습니다. xargs명령이 한 번만 실행 되더라도 호출 된 명령이 실행되는 동안 항상 메모리에 남아 있습니다.


네이티브 bash 메소드에 반대하는 것은 다음과 같습니다.

$ time { printf '%s\0' -- {1..1302581} | xargs -x0n 2076858 -s 2076858 /bin/true; }

real    0m2.014s
user    0m2.008s
sys     0m0.172s

$ time {
  args=()
  while IFS= read -rd '' arg; do
    args+=( "$arg" )
  done < <(printf '%s\0' -- $(echo {1..1302581}))
  /bin/true "${args[@]}"
}
bash: /bin/true: Argument list too long

real    107m51.876s
user    107m38.532s
sys     0m7.940s

bash는 배열 처리에 최적화되어 있지 않습니다.


남자 xargs :

-n 최대 값

명령 행당 최대 max-args 인수를 사용하십시오. -x 옵션을 지정하지 않은 경우 크기를 초과하면 (-s 옵션 참조) max-args보다 적은 인수가 사용됩니다.이 경우 xargs가 종료됩니다.

-s 최대 문자

명령 및 초기 인수 및 인수 문자열의 끝에있는 종료 널을 포함하여 명령 행당 최대 최대 문자 수를 사용하십시오. 허용되는 최대 값은 시스템에 따라 다르며 exec의 인수 길이 제한으로, 환경의 크기가 적고, 2048 바이트의 헤드 룸이 아닙니다. 이 값이 128KiB보다 크면 128Kib가 기본값으로 사용됩니다. 그렇지 않으면 기본값은 최대입니다. 1KiB는 1024 바이트입니다.

-엑스

크기가 초과되면 종료하십시오 (-s 옵션 참조).


답변