다음 줄로 프로그램을 작성한다고 가정해 보겠습니다.
int main(int argc, char** argv)
이제 .txt의 내용을 검사하여 어떤 명령줄 인수가 전달되었는지 알 수 있습니다 argv
.
프로그램이 인수 사이에 공백이 몇 개 있는지 감지할 수 있습니까? bash에 다음을 입력할 때와 같습니다.
ibug@linux:~ $ ./myprog aaa bbb
ibug@linux:~ $ ./myprog aaa bbb
환경은 최신 Linux(예: Ubuntu 16.04)이지만 대답은 모든 POSIX 호환 시스템에 적용되어야 한다고 생각합니다.
답변1
일반적으로 말하면 그렇지 않습니다. 명령줄 구문 분석은 셸에 의해 수행되며, 호출된 프로그램에서 구문 분석되지 않은 행을 사용할 수 있도록 만들지 않습니다. 실제로 문자열을 구문 분석하는 것이 아니라 프로그래밍 방식으로 인수 배열을 구성하여 argv를 생성하는 다른 프로그램에서 프로그램을 실행할 수 있습니다.
답변2
"인수 사이의 공간"에 대해 이야기하는 것은 쉘 개념입니다.
셸의 역할은 전체 입력 줄을 가져와 명령을 실행하기 위한 인수 배열로 구성하는 것입니다. 여기에는 인용된 문자열 구문 분석, 변수 확장, 파일 와일드카드 및 물결표 표현식 등이 포함될 수 있습니다. 이 명령은 exec
문자열 벡터를 허용하는 표준 시스템 호출로 시작됩니다 .
문자열 벡터를 생성하는 다른 방법이 있습니다. 많은 프로그램은 예약된 명령 호출을 통해 자체 하위 프로세스를 분기하고 실행합니다. 이 경우 "명령줄"과 같은 것은 없습니다. 마찬가지로 그래픽(데스크톱) 셸은 사용자가 파일 아이콘을 끌어서 명령 위젯에 놓으면 프로세스를 시작할 수 있습니다. 다시 말하지만 인수 "사이"에 문자가 포함된 텍스트 줄이 없습니다.
쉘이나 다른 상위/선구 프로세스에서 발생하는 일은 호출되는 명령에 관한 한 개인적이고 숨겨져 있습니다. main()
표준 C가 허용하는 문자열 배열만 볼 수 있습니다.
답변3
아니요, 공백이 없으면 불가능합니다.부분논쟁.
명령은 배열의 개별 매개변수에 액세스하고(프로그래밍 언어에 따라 어떤 형태로든) 실제 명령줄은 기록 파일에 저장될 수 있습니다(프롬프트에 기록 파일이 입력된 쉘에서 대화형인 경우). 그러나 어떤 형태로든 명령에 전달되지는 않습니다.
Unix의 모든 명령은 궁극적으로 exec()
이 기능 계열 중 하나에 의해 실행됩니다. 명령 이름과 매개변수 목록 또는 배열을 사용합니다. 그 중 어느 것도 쉘 프롬프트에 입력된 명령줄을 사용하지 않습니다. 함수 system()
는 이 작업을 수행하지만 해당 문자열 인수는 나중에 execve()
명령줄 문자열이 아닌 인수 배열을 사용하는 에 의해 실행됩니다.
답변4
언제든지 쉘이 애플리케이션을 실행하게 하는 쉘 코드를 애플리케이션에 알려주도록 할 수 있습니다. 예를 들어, 후크를 사용하여 해당 정보를 환경 변수에 전달합니다 zsh
(프로그램에서 사용할 예).$SHELL_CODE
preexec()
printenv
getenv("SHELL_CODE")
$ preexec() export SHELL_CODE=$1
$ printenv SHELL_CODE
printenv SHELL_CODE
$ printenv SHELL_CODE
printenv CODE
$ $(echo printenv SHELL_CODE)
$(echo printenv SHELL_CODE)
$ for i in SHELL_CODE; do printenv "$i"; done
for i in SHELL_CODE; do printenv "$i"; done
$ printenv SHELL_CODE; : other command
printenv SHELL_CODE; : other command
$ f() printenv SHELL_CODE
$ f
f
이 모든 작업은 다음 printenv
과 같이 실행됩니다.
execve("/usr/bin/printenv", ["printenv", "SHELL_CODE"],
["PATH=...", ..., "SHELL_CODE=..."]);
이러한 매개변수를 사용하여 실행을 유발한 zsh 코드를 printenv
검색 할 수 있습니다 . printenv
이 정보로 무엇을 하려는지 명확하지 않습니다.
의 경우 s에 bash
가장 가까운 zsh
함수는 이를 트랩에서 사용 preexec()
하지만 어느 정도 재작성이 있을 것이며(특히 구분 기호로 사용된 일부 공백을 리팩터링) 이는 실행되는 모든 (글쎄, 일부) 명령에 적용됩니다. 프롬프트에 입력된 전체 명령줄 대신(이 옵션도 참조)$BASH_COMMAND
DEBUG
bash
functrace
$ trap 'export SHELL_CODE="$BASH_COMMAND"' DEBUG
$ printenv SHELL_CODE
printenv SHELL_CODE
$ printenv $(echo 'SHELL_CODE')
printenv $(echo 'SHELL_CODE')
$ for i in SHELL_CODE; do printenv "$i"; done; : other command
printenv "$i"
$ printf '%s\n' "$(printenv "SHELL_CODE")"
printf '%s\n' "$(printenv "SHELL_CODE")"
$ set -o functrace
$ printf '%s\n' "$(printenv "SHELL_CODE")"
printenv "SHELL_CODE"
$ print${-+env } $(echo 'SHELL_CODE')
print${-+env } $(echo 'SHELL_CODE')
쉘 언어 구문에서 구분 기호로 사용되는 일부 공백이 1로 압축되는 방식과 전체 명령줄이 항상 명령에 전달되지 않는 방식을 알아보세요. 따라서 귀하의 경우에는 유용하지 않을 수 있습니다.
다음과 같이 각 명령에 민감한 정보가 유출될 수 있으므로 이 방법은 권장하지 않습니다.
echo very_secret | wc -c | untrustedcmd
wc
이 비밀을 및 에게 공개합니다 untrustedcmd
.
물론 쉘이 아닌 다른 언어에서도 가능합니다. 예를 들어 C에서는 일부 매크로를 사용하여 명령을 실행하는 C 코드를 환경으로 내보낼 수 있습니다.
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
#define WRAP(x) (setenv("C_CODE", #x, 1), x)
int main(int argc, char *argv[])
{
if (!fork()) WRAP(execlp("printenv", "printenv", "C_CODE", NULL));
wait(NULL);
if (!fork()) WRAP(0 + execlp("printenv", "printenv", "C_CODE", NULL));
wait(NULL);
if (argc > 1 && !fork()) WRAP(execvp(argv[1], &argv[1]));
wait(NULL);
return 0;
}
예:
$ ./a.out printenv C_CODE
execlp("printenv", "printenv", "C_CODE", NULL)
0 + execlp("printenv", "printenv", "C_CODE", NULL)
execvp(argv[1], &argv[1])
Bash의 경우처럼 C 전처리기가 특정 공간을 어떻게 압축하는지 알아보세요. 대부분의 (전부는 아니지만) 언어에서 구분 기호에 사용되는 공간의 양에는 차이가 없으므로 컴파일러/통역사가 여기에서 구분 기호를 어느 정도 자유롭게 사용하는 것은 놀라운 일이 아닙니다.