프로그램이 POSIX에서 명령 행 인수 사이에 공백을 얻을 수 있습니까?


23

다음 줄로 프로그램을 작성했는지 말해보십시오.

int main(int argc, char** argv)

이제의 내용을 확인하여 어떤 명령 행 인수가 전달되는지 알고 argv있습니다.

프로그램이 인수 사이에 얼마나 많은 공백을 감지 할 수 있습니까? 내가 bash에 이것을 입력 할 때와 같이 :

ibug@linux:~ $ ./myprog aaa bbb
ibug@linux:~ $ ./myprog       aaa      bbb

환경은 최신 Linux (예 : Ubuntu 16.04)이지만 POSIX 호환 시스템에 답을 적용해야한다고 생각합니다.


22
호기심을 위해 왜 프로그램이 그것을 알아야합니까?
nxnev

2
@nxnev 나는 몇 가지 Windows 프로그램을 작성하고 거기에서 가능하다는 것을 알고 있으므로 Linux (또는 Unix)에 비슷한 것이 있는지 궁금합니다.
iBug

9
CP / M에서 프로그램이 자체 명령 줄을 구문 분석해야한다는 것을 모호하게 기억합니다. 이는 모든 C 런타임이 쉘 구문 분석기를 구현해야 함을 의미했습니다. 그리고 그들은 모두 약간 다르게했습니다.
Toby Speight

3
@iBug 있지만 명령을 호출 할 때 인수를 인용해야합니다. POSIX (및 유사한) 셸에서 수행되는 방식입니다.
Konrad Rudolph

3
@iBug, ... Windows는 Toby가 위의 CP / M에서 언급 한 것과 동일한 디자인입니다. 유닉스는 그렇게하지 않는다-호출 된 프로세스의 관점 에서 그것을 실행하는 명령 줄 없다.
Charles Duffy

답변:


39

"인수 사이의 공백"에 대해 말하는 것은 의미가 없습니다. 그것은 쉘 개념입니다.

쉘의 임무는 전체 입력 행을 인수로 배열하여 명령을 시작하는 것입니다. 따옴표 붙은 문자열 구문 분석, 변수 확장, 파일 와일드 카드 및 물결표 식 등이 포함될 수 있습니다. 명령은 exec문자열 벡터를 허용하는 표준 시스템 호출 로 시작 됩니다.

문자열 벡터를 생성하는 다른 방법이 있습니다. 많은 프로그램이 미리 정해진 명령 호출로 자체 하위 프로세스를 포크하고 실행합니다.이 경우 "명령 줄"과 같은 것은 없습니다. 마찬가지로, 그래픽 (데스크톱) 쉘은 사용자가 파일 아이콘을 끌어서 명령 위젯에 놓을 때 프로세스를 시작할 수 있습니다.

호출 된 명령에 관한 한, 쉘 또는 다른 상위 / 전구 자 프로세스에서 진행되는 작업은 개인용이며 숨겨져 main()있습니다. 표준 C에서 허용 할 수있는 문자열 배열 만 볼 수 있습니다.


좋은 대답-유닉스 초보자들에게 이것을 지적하는 것이 중요합니다. 유닉스 초보자들은 종종 실행 tar cf texts.tar *.txt하면 tar 프로그램이 두 개의 인수를 얻고 두 번째 인수 ( *.txt) 자체 를 확장해야 한다고 생각 합니다. 많은 사람들이 인수를 처리하는 자체 스크립트 / 프로그램을 작성하기 전에는 실제로 어떻게 작동하는지 알지 못합니다.
Laurence Renshaw

58

일반적으로 아닙니다. 명령 행 구문 분석은 구문 분석되지 않은 행을 호출 된 프로그램에서 사용할 수 없게하는 쉘에 의해 수행됩니다. 실제로 프로그램은 문자열을 구문 분석하는 것이 아니라 인수 배열을 프로그래밍 방식으로 구성하여 argv를 만든 다른 프로그램에서 실행될 수 있습니다.


9
언급하고 싶을 수도 있습니다 execve(2).
iBug

3
당신이 맞아요, 절름발이 변명으로 나는 현재 전화를 사용하고 있고 매뉴얼 페이지를 찾는 것이 약간 지루하다고 말할 수 있습니다 :-)
Hans-Martin Mosner

1
POSIX의 관련 섹션입니다.
Stephen Kitt

1
@ Hans-MartinMosner : Termux ...? ;-)
DevSolar

9
"일반적으로"는 가능한 복잡한 특수 사례를 인용하는 것을 막기위한 수단으로 사용되었습니다. 예를 들어 suid 루트 프로세스는 호출 쉘의 메모리를 검사하고 구문 분석되지 않은 명령 행 문자열을 찾을 수 있습니다.
한스 마틴 모스 너

16

공백이 인수의 일부 가 아닌 한 불가능합니다 .

명령은 배열 (프로그램 언어에 따라 한 형태 또는 다른 형태)에서 개별 인수에 액세스하고 실제 명령 행은 히스토리 파일 (히스토리 파일이있는 쉘의 대화식 프롬프트에 입력 된 경우)에 저장 될 수 있습니다. 어떤 형태로든 명령에 전달되지 않았습니다.

유닉스의 모든 명령은 결국 exec()함수 계열 중 하나에 의해 실행됩니다 . 이들은 명령 이름과 인수 목록 또는 배열을 사용합니다. 그들 중 어느 것도 쉘 프롬프트에서 입력 된 명령 행을 취하지 않습니다. 이 system()함수는 수행하지만 문자열 인수는 나중에 execve()명령 행 문자열이 아닌 인수 배열을 사용하여 나중에 실행됩니다 .


2
@LightnessRacesinOrbit 나는 "인수 사이의 공백"에 대해 약간의 혼동이있는 경우를 대비하여 거기에 넣었다. 사이 따옴표로 공간을 퍼팅 hello하고하는 world것입니다 문자 그대로 두 인자 사이의 공간.
Kusalananda

5
@Kusalananda는 - 음, 아니 ... 사이 따옴표에 공백을 넣는 hello하고 world있습니다 말 그대로 세 가지 인수의 두 번째 공급.
Jeremy Jeremy

@Jeremy 내가 말했듯이, "논쟁 사이"의 의미에 대해 혼동이있는 경우. 당신이 원한다면 다른 두 사람 사이 의 두 번째 논쟁으로 그렇습니다 .
Kusalananda

귀하의 예는 훌륭하고 유익했습니다.
제레미

1
글쎄, 그 예들은 명백한 혼란과 오해의 원천이었습니다. 답변의 가치에 추가하지 않았으므로 삭제했습니다.
Kusalananda

9

일반적으로 다른 여러 답변과 같이 불가능합니다.

그러나 유닉스 쉘 입니다 일반 프로그램 (그들은 명령 행을 해석하고있다 로빙 즉, 그것을 확장 하기 전에 명령을 fork& execve그것을 위해). 쉘 조작 에 대한bash설명을 참조하십시오 . 당신은 당신 자신의 쉘을 작성할 수도 있고 (또는 GNU bash 와 같은 기존의 자유 소프트웨어 쉘을 패치 할 수도 있습니다) 그것을 쉘 (또는 로그인 쉘, passwd (5) & shells (5) 참조 )로 사용할 수도 있습니다.

예를 들어, 당신은 할 수있는 당신의 자신의 쉘 프로그램이 일부 환경 변수에 전체 명령 줄을 넣어 (상상 MY_COMMAND_LINE예를 들어) - 또는 다른 종류의 사용 프로세스 간 통신을 쉘에서 자식 프로세스 레에 명령 줄을 전송합니다.

왜 그렇게하고 싶은지 이해하지 못하지만 그러한 방식으로 동작하는 쉘을 코딩 할 수도 있습니다 (그러나 그렇게하지 않는 것이 좋습니다).

BTW, 프로그램은 쉘 이 아닌 일부 프로그램에 의해 시작될 수 있습니다 (그러나 fork (2) 다음 execve (2) 또는 execve현재 프로세스에서 프로그램을 시작하는 프로그램). 이 경우 명령 줄이 전혀 없으며 명령없이 프로그램을 시작할 수 있습니다 ...

쉘이 설치되지 않은 일부 (전문화 된) Linux 시스템이있을 수 있습니다. 이것은 이상하고 이례적이지만 가능합니다. 그런 다음 전문 작성해야 초기화 로 다른 프로그램을 시작 프로그램을 필요로 - 어떤 쉘을 사용하지 않고 있지만 수행하여 forkexecve시스템 호출.

또한 읽기 운영 체제 : 세 가지 쉬운 조각 하고 그 잊지 마세요 execve거의 항상이다 시스템 호출 (리눅스, 그들이에 나와있는 콜 (2) 참조 또한 소개 (2) )을 다시 초기화 가상 주소 공간 (및 일부 다른 프로세스 ) .


이것이 가장 좋은 대답입니다. 나는 그 (즉 최대를보고하지 않고) 가정 argv[0] 프로그램 이름과 인수에 대한 나머지 요소에 대한 POSIX 사양하고 변경할 수 없습니다. 런타임 환경은 argv[-1]명령 줄을 지정할 수 있습니다 .
Peter-Reinstate Monica

아뇨. 더 자세히 execve문서를 읽으십시오 . 을 (를) 사용할 수 없습니다.이를 사용 argv[-1]하는 것은 정의되지 않은 동작입니다.
Basile Starynkevitch

예, 좋은 지적 (또한 우리가 syscall을 가지고 있다는 힌트)-아이디어는 약간 고안되었습니다. 런타임의 세 가지 구성 요소 (shell, stdlib 및 OS)는 모두 협업해야합니다. 쉘 execvepluscmd은 여분의 매개 변수 (또는 argv 규칙)를 사용 하여 특수한 비 POSIX 함수 를 호출해야하며 , syscall은 프로그램 이름에 대한 포인터 앞에 명령 행에 대한 포인터를 포함하는 main에 대한 인수 벡터를 구성한 다음 주소를 전달합니다. 등의 프로그램 이름에 대한 포인터의 argv프로그램의를 호출 할 때 main...
피터 - 분석 재개 모니카

쉘을 다시 작성할 필요가 없으며 따옴표 만 사용하십시오. 이 기능은 Bourn 쉘에서 사용할 수 있습니다 sh. 새로운 것이 아닙니다.
ctrl-alt-delor

따옴표를 사용 하려면 명령 줄 을 변경 해야합니다 . 그리고 OP는 원하지 않습니다
Basile Starynkevitch

3

쉘에게 항상 어떤 쉘 코드가 실행을하는지 알려주도록 쉘에 지시 할 수 있습니다. 예를 들어 with를 사용하여 후크를 사용하여 환경 변수 zsh에 해당 정보를 전달 하면 (예를 들어 프로그램에서 사용 ) :$SHELL_CODEpreexec()printenvgetenv("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=..."]);

해당 인수로 printenv실행되는 zsh 코드를 검색 할 수 printenv있습니다. 그 정보로 당신이하고 싶은 것은 분명하지 않습니다.

bash기능에 가장 가까운, zshS는 ' preexec()그것을 사용하는 것입니다 $BASH_COMMANDA의 DEBUG함정,하지만 노트 bash(잘 일부) (특히 refactors에 구분 기호로 사용되는 공백의 일부 등)와 그의 모든에 적용한다는 점에서 다시 작성 일정 수준의 수행 명령 프롬프트에서 입력 한 전체 명령 줄이 아니라 실행합니다 ( 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 전 처리기에서 일부 공간이 어떻게 압축되었는지 확인하십시오. 모든 언어는 아니지만 대부분의 언어에서 구분 기호에 사용되는 공간의 양에는 차이가 없으므로 컴파일러 / 통역사가 여기에서 자유 로워지는 것은 놀라운 일이 아닙니다.


이것을 테스트 할 때 BASH_COMMAND원래 공백을 구분하는 인수를 포함하지 않았으므로 OP의 리터럴 요청에는 사용할 수 없었습니다. 이 답변에는 특정 사용 사례에 대한 데모가 포함되어 있습니까?
Charles Duffy

@CharlesDuffy, 방금 bash에서 zsh의 preexec ()와 가장 가까운 것을 표시하고 싶었습니다 (OP가 참조하는 쉘이므로) 특정 사용 사례에 사용할 수는 없다는 것을 지적했지만 매우 명확한. 편집을 참조하십시오. 이 답변은 실행을 명령으로 실행시키는 소스 코드 (여기서는 zsh / bash / C)를 전달하는 방법에 대해보다 일반적으로 작성되었습니다. 유용하지는 않지만 특히 그렇게하기를 바랍니다. 예를 들어, 나는 그것이별로 유용하지 않음을 보여줍니다)
Stéphane Chazelas

0

나는 다른 답변에서 누락 된 것을 추가 할 것입니다.

아니

다른 답변보기

아마도

프로그램에서 수행 할 수있는 작업은 없지만 프로그램을 실행할 때 셸에서 수행 할 수있는 작업이 있습니다.

따옴표를 사용해야합니다. 그래서 대신

./myprog      aaa      bbb

이 중 하나를 수행해야합니다

./myprog "     aaa      bbb"
./myprog '     aaa      bbb'

이것은 공백과 함께 하나의 인수를 프로그램에 전달합니다. 둘 사이에는 차이가 있으며, 두 번째는 리터럴이며, 표시된 그대로 정확하게 문자열입니다 (를 '입력해야합니다 \'). 첫 번째 문자는 일부 문자를 해석하지만 여러 인수로 나뉩니다. 자세한 내용은 쉘 인용을 참조하십시오. 따라서 셸을 다시 작성할 필요가 없습니다. 셸 디자이너는 이미 생각했습니다. 그러나 이제는 하나의 주장이므로 프로그램 내에서 더 많은 전달을해야합니다.

옵션 2

stdin을 통해 데이터를 전달하십시오. 이것은 많은 양의 데이터를 명령으로 가져 오는 일반적인 방법입니다. 예 :

./myprog << EOF
    aaa      bbb
EOF

또는

./myprog
Tell me what you want to tell me:
aaaa bbb
ctrl-d

(이탤릭체는 프로그램의 출력입니다)


기술적으로, 쉘 코드 : ./myprog␣"␣␣␣␣␣aaa␣␣␣␣␣␣bbb"(일반적으로 자식 프로세스에서) 실행 파일에 저장 ./myprog과 전달 이 개 인수를 : ./myprog␣␣␣␣␣aaa␣␣␣␣␣␣bbb( argv[0]argc[1], argc2 인)과 영업 이익의 같이 두 인수를 구분하는 공간은 어떤 방식으로 전달되지 않습니다 에 myprog.
Stéphane Chazelas

그러나 당신은 명령을 변경하고 있으며, OP는 변경하고 싶지 않습니다
Basile Starynkevitch

@BasileStarynkevitch 귀하의 의견에 따라, 나는 질문을 다시 읽었습니다. 당신은 가정하고 있습니다. OP는 프로그램 실행 방식을 바꾸고 싶지 않다고 말합니다. 어쩌면 이것은 사실이지만, 아무 말도하지 않았습니다. 따라서이 답변이 필요할 수도 있습니다.
ctrl-alt-delor

OP 는 공백 포함하는 하나의 단일 인수가 아니라 인수 사이의 공백 대해 명시 적으로 묻습니다
Basile Starynkevitch
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.