매뉴얼 페이지를 읽고 있는데 find
다음 명령에 대해 혼란스러워졌습니다. 하나와 해당하는 것의 차이점은 무엇입니까?
다음 두 명령의 차이점은 무엇입니까?
find -execdir command "{}" \; find -execdir "command {}" \;
혼동 이유: 인용은 쉘이 인용된 부분을 블록으로 처리하도록 지시해야 한다고 생각했습니다. 그래서 두 번째 것을 봤을 때 명령어가 없어서 실패할 줄 알았습니다 command <file-name>
.
다음 두 가지의 차이점은 무엇입니까?
find -execdir bash -c "command" "{}" \; find -execdir bash -c "command {}" \;
혼동 이유: 내가 이해한 바에 따르면 두 번째 버전에서는 명령이 중괄호로 묶여 있고 bash 명령 전체에 전달되어야 하며 find
중괄호는 해당 파일 이름으로 해석되어서는 안 됩니다.
다음 두 가지의 차이점은 무엇입니까?
find -execdir bash -c "something \"$@\"" {} \; find -execdir bash -c 'something "$@"' bash {} \;
내가 이해하는 한, 둘 다 동일합니다. 중괄호를 셸에 전달하는 방법은 두 번째 버전이 첫 번째 버전보다 안전합니다.
고쳐 쓰다
질문 #3에 있는 명령의 첫 번째 버전이 작동하지 않는다는 사실을 발견했습니다! 다음을 시도했습니다(아무도 작동하지 않았습니다).
find -execdir bash -c 'something \"$@\"' {} \; # changed external quotes to single ones.
find -execdir bash -c "something $@" {} \; # removed the inner quotes.
find -execdir bash -c 'something $@' {} \; # same as above but with outer single quotes instead.
내가 여기서 무엇을 놓치고 있는 걸까요? 명령 따옴표 바깥에 중괄호를 넣을 수 있어야 한다고 생각합니다.
답변1
이것은 (당신도 알고 있듯이) 매우 복잡합니다. 나는 그것을 설명하려고 노력할 것입니다. 명령이 수행하는 구문 분석/처리 순서에 대해 생각하고 각 단계에서 어떤 일이 발생하는지 관찰하는 것이 도움이 됩니다. 일반적으로 프로세스는 다음과 같습니다.
- 쉘은 명령줄을 구문 분석하여 토큰("단어")으로 나누고, 변수 참조 등을 바꾸고, 따옴표와 이스케이프를 제거합니다(해당 효과를 적용한 후). 그런 다음 (보통) 첫 번째 "단어"를 명령 이름(이 경우 "find")으로 실행하고 나머지 단어를 인수로 전달합니다.
find
파일을 검색하고 "-execdir
"과 " ";
사이의 내용을 명령으로 실행합니다 . " "를 일치하는 파일 이름으로 바꾸지{}
만 다른 구문 분석은 수행하지 않습니다.-execdir
명령 이름으로 " " 뒤의 첫 번째 인수를 실행하고 다음 인수를 전달합니다.그것은논쟁.- 명령이 발생하고 옵션이
bash
전달되면-c
다음 인수를-c
명령 문자열(일종의 미니 쉘 스크립트와 유사)로 구문 분석하고 나머지 인수를 해당 미니 스크립트에 대한 인수로 구문 분석합니다.
자, 더 진행하기 전에 몇 가지 추가 사항을 말씀드리겠습니다. 저는 BSD를 사용하고 있는데 find
, 검색할 디렉토리를 명시적으로 지정해야 하므로 find . -execdir ...
단순히 를 사용하는 대신 find -execdir ...
제가 있는 디렉토리에 "foo.txt ” 파일이 포함되어 있습니다. 및 “z@$%^;*;echo wheee.jpg”(잘못된 사용의 위험을 설명하기 위해 bash -c
). 마지막으로, pargs
인수를 인쇄하는(아무 것도 얻지 못하면 불평하는) 바이너리 디렉토리의 짧은 스크립트를 호출했습니다 .
질문 1:
이제 첫 번째 질문의 두 명령을 시도해 보겠습니다.
$ find . -execdir pargs "{}" \;
pargs got 1 argument(s): '.'
pargs got 1 argument(s): 'foo.txt'
pargs got 1 argument(s): 'z@$%^;*;echo wheee.jpg'
$ find . -execdir "pargs {}" \;
find: pargs .: No such file or directory
find: pargs foo.txt: No such file or directory
find: pargs z@$%^;*;echo wheee.jpg: No such file or directory
이는 예상한 대로 수행됩니다. 첫 번째는 작동하지만(btw, 주변의 큰따옴표를 생략할 수 있음 {}
) 두 번째는 실패합니다. 공백과 파일 이름은 인수가 아닌 명령 이름의 일부로 간주되기 때문입니다.
-exec[dir] ... +
그런데 --this를 사용하여 가능한 적은 수의 명령으로 명령을 실행하고 한 번에 여러 파일 이름을 전달하도록 -exec[dir] \;
지시하는 것도 가능합니다 .find
$ find . -execdir pargs {} +
pargs got 3 argument(s): '.' 'foo.txt' 'z@$%^;*;echo wheee.jpg'
질문 2:
이번에는 한 번에 하나의 옵션을 선택하겠습니다.
$ find . -execdir bash -c "pargs" "{}" \;
pargs didn't get any arguments
pargs didn't get any arguments
pargs didn't get any arguments
"흠"이라고요? 여기서 일어나는 일은 bash
" ", " ", " "와 같은 매개변수 목록을 사용하여 실행되는 것입니다. 이 옵션은 다음과 같이 미니 쉘 스크립트처럼 다음 인수("pargs")를 실행하도록 지시합니다.-c
pargs
foo.txt
-c
bash
#!/bin/bash
pargs
...그리고 어떻게든 "미니 스크립트" 매개변수 "foo.txt"를 전달합니다(자세한 내용은 나중에 설명). 그러나 미니 스크립트는 인수에 대해 아무 작업도 수행하지 않습니다. 특히 인수를 명령에 전달하지 않으므로 pargs
아무 pargs
것도 표시되지 않습니다. (이 작업을 수행하는 올바른 방법은 세 번째 질문에서 찾아보겠습니다.) 이제 두 번째 질문의 두 번째 대안을 시도해 보겠습니다.
$ find . -execdir bash -c "pargs {}" \;
pargs got 1 argument(s): '.'
pargs got 1 argument(s): 'foo.txt'
pargs got 1 argument(s): 'z@$%^'
bash: foo.txt: command not found
wheee.jpg
현재 모든 것이 작동하고 있지만 부분적으로만 작동합니다. bash
인수 " -c
" 및 "pargs" + 파일 이름을 사용하여 실행하면 "."에서 예상대로 작동합니다. 및 "foo.txt"이지만 " " 및 " " bash
매개변수를 전달하면 이제 다음과 같은 미니 스크립트가 실행됩니다.-c
pargs z@$%^;*;echo wheee.jpg
#!/bin/bash
pargs z@$%^;*;echo wheee.jpg
따라서 bash는 이를 세미콜론으로 구분된 세 개의 명령으로 분할합니다.
- "
pargs z@$%^
" (당신이 보는 효과) - " "는 " " 및 " "라는
*
단어로 확장되므로 명령으로 실행하고 다른 파일 이름을 인수로 전달해 보십시오. 해당 이름의 명령이 없으므로 적절한 오류가 발생합니다.foo.txt
z@$%^;*;echo wheee.jpg
foo.txt
- "
echo echo wheee.jpg
", 터미널에 "wheee.jpg"를 인쇄하는 것을 볼 수 있듯이 이것은 완벽하게 합리적인 명령입니다.
따라서 일반 이름을 가진 파일에서는 작동하지만 셸 구문이 포함된 파일 이름을 발견하면 파일 이름의 일부를 실행하려고 시도하기 시작합니다. 그렇기 때문에 이 방법은 안전하지 않습니다.
질문 3:
다시 한 번 한 가지 옵션을 살펴보겠습니다.
$ find . -execdir bash -c "pargs \"$@\"" {} \;
pargs got 1 argument(s): ''
pargs got 1 argument(s): ''
pargs got 1 argument(s): ''
$
다시 한 번 "응???"이라고 말하는 것을 들었습니다. 여기서 가장 큰 문제는 $@
이스케이프되거나 작은 따옴표로 묶이지 않아서 현재 쉘 컨텍스트에 의해 확장된다는 것입니다.앞으로에 전달되었습니다 . 여기에서 실제로 얻은 매개변수를 표시 find
하는 데 사용하겠습니다 .pargs
find
$ pargs . -execdir bash -c "pargs \"$@\"" {} \;
pargs got 7 argument(s): '.' '-execdir' 'bash' '-c' 'pargs ""' '{}' ';'
$@
대화형 셸에서 실행 중이었고 인수를 받지 못했기 때문에(또는 명령을 사용하여 설정하지 않았기 때문에 그냥 사라졌습니다 . set
) 그래서 우리는 다음과 같은 미니 스크립트를 실행하고 있습니다.
#!/bin/bash
pargs ""
... pargs
빈 인수를 얻는 이유를 설명합니다.
이게 스크립트에 있었다면가지다인수가 수신되면 상황은 더욱 혼란스러워집니다. 이스케이프(또는 작은따옴표)를 사용하면 $
문제가 해결되지만 여전히 제대로 작동하지 않습니다.
$ find . -execdir bash -c 'pargs "$@"' {} \;
pargs didn't get any arguments
pargs didn't get any arguments
pargs didn't get any arguments
여기서 문제는 bash
미니스크립트 뒤의 다음 매개변수가 미니스크립트의 이름으로 처리된다는 것입니다(미니스크립트는 으로 사용할 수 $0
있지만아니요$@
일반 인수(예: )가 아닌 ) 에 포함됩니다 $1
. 다음은 이 기능을 보여주는 일반 스크립트입니다.
$ cat argdemo.sh
#!/bin/bash
echo "My name is $0; I received these arguments: $@"
$ ./argdemo.sh foo bar baz
My name is ./argdemo.sh; I received these arguments: foo bar baz
이제 비슷한 bash -c
미니 스크립트를 사용해 보세요.
$ bash -c 'echo "My name is $0; I received these arguments: $@"' foo bar baz
My name is foo; I received these arguments: bar baz
이 문제를 해결하는 표준 방법은 실제 매개변수가 일반적인 방식으로 표시되도록 더미 스크립트 이름 매개변수(예: "bash")를 추가하는 것입니다.
$ bash -c 'echo "My name is $0; I received these arguments: $@"' mini-script foo bar baz
My name is mini-script; I received these arguments: foo bar baz
이것이 바로 두 번째 옵션이 수행하는 작업입니다. "bash"를 스크립트 이름으로 전달하고 발견된 파일 이름을 다음과 같이 전달합니다 $1
.
$ find . -execdir bash -c 'pargs "$@"' bash {} \;
pargs got 1 argument(s): '.'
pargs got 1 argument(s): 'foo.txt'
pargs got 1 argument(s): 'z@$%^;*;echo wheee.jpg'
결국 이상한 파일 이름을 사용해도 작동했습니다. 이것이 바로 이 옵션(또는 첫 번째 질문의 첫 번째 옵션)이 사용되는 것으로 간주되는 이유입니다 find -exec[dir]
. -exec[dir] ... +
다음과 함께 사용할 수도 있습니다.
$ find . -execdir bash -c 'pargs "$@"' bash {} +
pargs got 3 argument(s): '.' 'foo.txt' 'z@$%^;*;echo wheee.jpg'