"sudo -i" 로그인 쉘이 here-doc 명령 문자열 인수를 중단하는 이유는 무엇입니까?

"sudo -i" 로그인 쉘이 here-doc 명령 문자열 인수를 중단하는 이유는 무엇입니까?

아래의 5개 명령 시퀀스에서 모든 명령은 작은따옴표를 사용하여 호출 bash셸이 아닌 호출 셸에 가능한 변수 대체를 전달합니다. 통화 사용자는더블 엑스하지만 호출되는 쉘은 사용자로 실행됩니다.. 호출 셸은 로그인 셸이 아니기 때문에 첫 번째 명령은 $HOME을 호출 셸의 값으로 바꿉니다. 두 번째 명령은 로그인 쉘에 의해 로드된 $HOME 값을 대체하므로 이는 사용자에게 속한 값입니다.. 세 번째 명령은 $HOME 값에 의존하지 않고 추측된 사용자의 홈 디렉터리에 파일을 생성합니다..

네 번째 명령이 실패하는 이유는 무엇입니까? 의도는 동일한 파일에 쓰지만 사용자에 속한 $HOME 변수에 의존한다는 것입니다.그것이 그녀의 홈 디렉토리에 있는지 확인하십시오. 로그인 쉘이 정적 작은따옴표 문자열로 전달된 here-doc 명령의 동작을 중단하는 이유를 이해할 수 없습니다. 다섯 번째 명령이 실패하면 문제가 변수 대체와 관련이 없음이 확인됩니다.

xx@host ~ $ sudo -u yy bash -c 'echo HOME=$HOME'
HOME=/home/xx
xx@host ~ $ sudo -iu yy bash -c 'echo HOME=$HOME'
HOME=/home/yy
xx@host ~ $ sudo -u yy bash -c 'cat > /home/yy/test.sh << "EOF"
> script-content
> EOF
> '
xx@host ~ $ sudo -iu yy bash -c 'cat > $HOME/test.sh << "EOF"
> script-content
> EOF
> '
bash: warning: here-document at line 0 delimited by end-of-file (wanted `EOFscript-contentEOF')
xx@host ~ $ sudo -iu yy bash -c 'cat > /home/yy/test.sh << "EOF"
> script-content
> EOF
> '
bash: warning: here-document at line 0 delimited by end-of-file (wanted `EOFscript-contentEOF')

이 명령은 Ubuntu 16.04(Xenial Xerus) 기반 Linux Mint 18.3 Cinnamon 64비트 시스템에서 실행되었습니다.

고쳐 쓰다:여기서 문서화 측면은 문제를 모호하게 만듭니다. 이것은 문제를 단순화한 것입니다:

$ sudo bash -c 'echo 1
> echo 2'
1
2
$ sudo -i bash -c 'echo 1
> echo 2'
1echo 2

이 두 명령 중 첫 번째 명령은 개행 문자를 유지하고 두 번째 명령은 유지하지 않는 이유는 무엇입니까? sudo두 명령 모두 공통이지만 "-i" 옵션에 따라 이스케이프/필터링/보간이 다르게 수행되는 것 같습니다.

답변1

이것선적 서류 비치-i상태:

(초기 로그인 시뮬레이션) 옵션은 -i대상 사용자의 비밀번호 데이터베이스 항목에 지정된 쉘을 로그인 쉘로 실행합니다. 이는 쉘이 .profile 또는 .login과 같은 로그인별 리소스 파일을 읽는다는 것을 의미합니다.명령이 지정되면 쉘의 -c 옵션을 통해 실행할 수 있도록 쉘에 전달됩니다.

즉, 실제로 사용자의 로그인 셸을 실행한 다음 다음 sudo을 사용하여 제공하는 모든 명령을 전달합니다 -c.같지 않은sudo cmd arg arg해당 옵션 없이 일반적으로 수행되는 작업입니다 -i. 일반적으로 sudo이들 중 하나만 사용됩니다.exec*기능프로세스 자체는 중간 쉘 없이 직접 시작되며 모든 매개변수는 있는 그대로 전달됩니다.

를 사용하면 -i사용자의 셸을 로그인 셸로 실행하도록 환경을 설정하고 인수로 실행하도록 요청한 명령을 재구성합니다 bash -c. 귀하의 경우에는 (대략적으로) 실행됩니다 /bin/bash -c "bash -c ' ... '"(참조가 유효하다고 상상해보십시오).

문제는 sudo작성한 명령을 -c처리할 수 있는 명령으로 바꾸는 방법입니다., 다음 섹션에서 설명합니다. 마지막 부분에는 몇 가지 가능한 해결 방법이 있고 중간에는 몇 가지 디버깅 및 검증 기술이 있습니다.


왜 이런 일이 발생합니까?

명령이 전달되면 -c올바른 작업을 수행하기 위해 일부 전처리가 필요하며 이는 sudo셸을 실행하기 전에 발생합니다. 예를 들어, 명령이 다음과 같은 경우:

sudo -iu yy echo 'three   spaces'

그런 다음 명령이 동일한 의미를 갖도록 이러한 공백을 이스케이프해야 합니다(즉, 단일 인수가 두 단어로 분할되지 않습니다). 결국 실행된 내용은 다음과 같습니다.

/bin/bash -c 'echo three\ \ \ spaces'

간단한 명령부터 시작해 보겠습니다.

sudo bash -c 'echo 1
echo 2'

이 경우 sudo사용자를 변경한 후 실행하세요.execvp("bash", \["bash", "-c", "echo 1\necho 2"\])(발명된 배열 리터럴 구문의 경우)

그리고 -i:

sudo -i bash -c 'echo 1
echo 2'

대신 사용자를 변경한 다음 실행합니다 execv("/bin/bash", ["-bash", "-c", "bash -c echo\\ 1\\\necho\\ 2"]). 여기서 \\는 리터럴과 동일 \하고 \n개행입니다. 주 명령에 백슬래시를 추가하여 공백과 줄 바꿈을 이스케이프합니다.

-c즉, 두 개의 인수 , 즉 쉘이 올바르게 이해할 것으로 예상하는 형식으로 재구성된 전체 명령을 사용하는 외부 로그인 쉘이 있습니다. 불행하게도 그렇지 않습니다. 내부 bash명령은 결국 실행을 시도합니다.

echo 1\
echo 2

첫 번째 실제 줄이 줄 연속 문자(백슬래시 뒤에 개행 문자가 오는 문자)로 끝나는 경우 해당 줄은 완전히 삭제됩니다. 논리적 라인은 단지 echo 1echo 2당신이 원하는 것을 하지 않는다는 것입니다.

sudo이는 s의 탈출에 결함이 있다는 견해가 있습니다 .백슬래시 및 개행의 표준 동작오른쪽. 나는 그들을 여기에 두는 것이 안전할 것이라고 생각했습니다.


여기 문서를 사용하는 명령에서도 마찬가지입니다. 대략 다음과 같이 작동합니다.

/bin/bash -c 'bash -c cat\ \<\<\ \"EOF\"\\012script-content\\012EOF\\012'

여기서 \012실제 개행 문자를 나타냅니다. sudo각 개행 문자 앞에 공백처럼 백슬래시를 삽입합니다. 이중 이스케이프 on 에 주목하세요 \\012. 이것은 ps실제 백슬래시와 개행문자를 표현한 것입니다. 여기서는 이를 사용합니다(아래 참조). 결국 실행된 내용은 다음과 같습니다.

bash -c 'cat << "EOF"\
script-content\
EOF\
'

그리고줄 연속 \+ 줄 바꿈여기저기에 있는데 방금 제거되었습니다. 이렇게 하면 실제 개행 문자가 없고 유효하지 않은 heredoc이 없는 하나의 긴 줄이 됩니다.

bash -c 'cat << "EOF"script-contentEOF'

문제는 다음과 같습니다. 내부 bash프로세스는 한 줄의 명령만 가져오므로 여기서는 문서가 끝나거나 시작할 기회가 없습니다. 내부적으로는 다소 불완전한 솔루션이 있지만 이것이 근본 원인입니다.


무슨 일이 일어나고 있는지 어떻게 확인하나요?

이 명령을 올바르게 인용하고 무슨 일이 일어나고 있는지 확인하기 위해 로그인 셸( .profile, .bash_profile, .zprofile등)의 구성 파일을 수정하여 다음과 같이 간단히 설명했습니다.

ps awx|grep $$

이는 당시 실행 중인 셸의 명령줄을 보여주고 경고 전에 몇 줄의 추가 출력 줄을 제공합니다.

hexdump -C /proc/$$/cmdlineLinux에도 도움이 됩니다.


이에 대해 무엇을 할 수 있나요?

나는 이것으로부터 당신이 원하는 것을 얻을 수 있는 명확하고 신뢰할 수 있는 방법을 찾지 못했습니다. sudo가능하다면 명령을 전혀 건드리고 싶지 않습니다. 간단한 경우에 주로 작동하는 한 가지 옵션은 명령줄에서 명령을 지정하는 대신 명령을 셸로 파이프하는 것입니다.

printf 'cat > ... << ... \n ...' | sudo -iu yy

여기에는 여전히 신중한 내부 탈출이 필요합니다.

아마도 더 나은 접근 방식은 임시 스크립트 파일에 넣고 다음과 같이 실행하는 것입니다.

f=`mktemp`
printf 'command' > "$f"
chmod +r "$f"
sudo -iu yy "$f"
rm "$f"

구성한 파일 이름도 작동합니다. sudoers 설정에 따라 /dev/fd/3실제로 디스크에 저장하고 싶지 않은 경우 파일 설명자를 열어두고 file()로 읽을 수 있지만 실제 파일이 더 쉬울 수 있습니다.

답변2

이를 밝혀내기 위한 보다 일반적인 요구는 터미널 세션에 직접 복사하여 붙여넣을 수 있는 일련의 명령을 기록하는 것입니다. 이러한 명령 중 일부는 사람이 읽을 수 있는 방식으로 작은 파일을 생성하면서도 각 단계를 문서화합니다. 임시 파일을 만들거나 독자에게 "편집기를 사용"하도록 지시하는 것은 본질적으로 100% 재생 가능성이라는 목표에서 벗어나는 것입니다. –4분 전

만약에그건원하는 작업을 수행하려면 다음을 사용하세요.

sudo -u yy tee ~yy/test.sh >/dev/null <<'EOF'
script-content
EOF

bash -c '...'훨씬 더 간단합니다. 전체 내용을 구문 에 붙여넣는 참조 문제를 해결할 필요가 없습니다 . 특히 스크립트에 따옴표가 있으면 작업이 더 쉬워집니다.

(여기에 참조된 구분 기호 정의는 다음과 같으 'EOF'므로 스크립트 내용에 사용하는 모든 변수는만들다test.sh, 이는 귀하의 의도가 아닐 수도 있습니다. )

yy또한 사용자 의 홈 디렉토리를 얻기 위해 물결표 확장을 사용하는 것에 유의하십시오 ~yy. LESS='+/^ *Tilde Expansion' man bash자세한 내용은 참고 자료를 참조하세요. 그렇기 때문에 그럴 필요가 없습니다 sudo -i.

관련 정보