SSH를 통해 복잡한 명령줄을 실행하는 방법은 무엇입니까?

SSH를 통해 복잡한 명령줄을 실행하는 방법은 무엇입니까?

가끔 원격 호스트에서 실행 중인 도커 컨테이너의 환경을 가져오고 싶을 때가 있습니다. 이를 위해 호스트에 로그인합니다.

ssh [email protected]

그런 다음 다음 명령을 실행합니다.

sudo docker exec -it `sudo docker ps | grep mycontainername | awk '{print $1;}'` env

이제 하나의 명령으로 이 작업을 수행하고 싶습니다(3회 이상 수행하고 자동화하고 싶습니다.. :-)).

이것은 작동합니다:

 ssh -t [email protected] sudo docker ps | grep mycontainername

하지만 내가 이것을 할 때

ssh -t [email protected] sudo docker exec -it `sudo docker ps | grep mycontainername | awk '{print $1;}'` env

나는 얻다

"docker exec" requires at least 2 arguments.
See 'docker exec --help'.

Usage:  docker exec [OPTIONS] CONTAINER COMMAND [ARG...]

Run a command in a running container
Connection to 123.456.789.10 closed.

내가 이것을 어떻게 실행할 수 있는지 아는 사람이 있습니까?

답변1

일반적인 관찰

Bash에는 뒤에 오는 내용을 구문 분석하는 데 영향을 미치는 몇 가지 키워드가 있습니다(예: [[. 그러나 ssh그 중 하나는 아니며 일반적인 명령입니다. 이는 다음을 의미합니다.

  • 전체 ssh …줄은 일반적으로 귀하에 의해 구문 분석됩니다.현지의껍데기; |, ;, *, "또는 공백 과 같은 문자는 셸에 의미가 있으므로 인용하거나 이스케이프 처리하지 않는 한 에 $도달하지 않습니다 (몇 가지 예외를 제외하고, 예를 들어 별도의 단어로 단독으로 사용하는 것은 특별하지 않습니다). 이것이 구문 분석 및 해석의 첫 번째 수준입니다.ssh$

  • ssh쉘이 작업을 수행한 후 인수(또는 다른 일반 명령) 에 도달하는 것은 무엇이든 인수, 문자열일 뿐입니다. 이제 이를 해석하는 것이 도구의 역할입니다. 이것이 두 번째 수준입니다.

ssh명령줄 인수 중 일부(0개, 1개 이상)가 서버 측에서 실행되는 명령으로 해석되는 경우 . 일반적으로 ssh여러 인수를 사용하여 명령을 작성할 수 있습니다. 그 효과는 마치 서버에서 다음과 같은 것을 호출한 것과 같습니다.

"$SHELL" -c "$command_line_built_by_ssh"

(저는 그렇다고 주장하는 것이 아닙니다.정확히하지만 무슨 일이 일어나는지 이해하기에 충분히 가깝습니다. 쉘에서 호출되는 것처럼 작성했기 때문에 친숙해 보입니다. 하지만 실제로는 아직 껍질이 없습니다. 그리고 변수가 없습니다 $command_line…. 이 답변의 목적으로 일부 문자열을 참조하기 위해 이 이름을 사용하고 있습니다.)

그런 다음 $SHELL서버에서 $command_line…자체적으로 구문 분석합니다. 이것이 세 번째 수준입니다.


특정 관찰

실패한 명령

ssh -t [email protected] sudo docker exec -it `sudo docker ps | grep mycontainername | awk '{print $1;}'` env

123.456.789.10유효한 IP 주소가 아니기 때문에 실패했습니다 .

알겠습니다. 123.456.789.10자리 표시자이지만 여전히 유효하지 않다는 점을 이해합니다. :)

명령이 sudo docker ps | grep mycontainername | awk '{print $1;}'로컬에서 실행되었기 때문에 실패했습니다. 출력이 비어 있을 수 있습니다. 그렇다면 $command_line_built_by_ssh당신이 원하는 것과는 거리가 멀었습니다.

참고는 로컬에서 ssh … | grep mycontainername실행됩니다 grep(이 사실을 알고 있을 수도 있고 모를 수도 있음).


논의

원격 셸이 얻을 수 있는 결과를 제어하려면 $command_line_built_by_ssh이전에 발생하는 구문 분석 및 해석을 이해하고 예측하고 주도해야 합니다. 로컬 명령을 작성해야 하므로~ 후에로컬 셸을 실행하고 이를 소화하면 원격 측에서 실행하려는 ssh정확한 내용이 됩니다 .$command_line…

결과가 ssh. 귀하가 원하는 축어적 문자열을 이미 가지고 있기 때문에 귀하의 경우는 더 간단합니다 $command_line_built_by_ssh. 문자열은 다음과 같습니다

sudo docker exec -it $(sudo docker ps | grep mycontainername | awk '{print $1;}') env

노트:

  • 백틱이 아닌 의 형태로 명령 대체를 사용했습니다 $(). 있다선호하는 이유$().
  • 전혀 모르겠습니다 . 큰따옴표를 사용해야 docker하는지 알 수 없습니다 . $(…)일반적으로인용하지 않는 것은 거의 항상 나쁘다. 대체가 여러 단어를 반환하는 경우(예: 여러 줄 입력 awk) 어떤 일이 발생하는지 자문해 보세요. 이것은 다른 문제(이 경우 문제가 되는 경우)이므로 이 답변에서는 다루지 않겠습니다.

\로컬 셸에서 모든 항목이 확장/해석되는 것을 방지하려면 확장을 트리거하거나 해석할 수 있는 모든 문자를 적절하게 인용하거나 이스케이프(사용)해야 합니다 . 이 경우 $, (, ), |, ;, {, }'(어쩌면 또는 선택적으로) 공백을 인용하십시오.

나는 작동 방식 때문에 "어쩌면 또는 선택적으로 공백을 인용하거나 이스케이프 처리할 수도 있습니다"라고 말했습니다 ssh … some command. 서버에서 실행될 코드로 해석되는 두 개 이상의 인수를 찾으면 그 사이에 단일 공백을 추가하여 이를 연결합니다. 이렇게 $command_line_built_by_ssh구축되었습니다. 원격 셸의 코드처럼 보이는 부분에서 공백을 인용하거나 이스케이프하지 않으면 로컬 셸은 단어를 분할하는 동안 공백(및 탭)을 사용한 다음 ssh공백을 추가합니다. 탭이 있거나 여러 개의 연속 공백이 있는 경우 결과가 정확히 원하는 결과가 아닐 수 있습니다. 예를 들어:

ssh user@server echo a     b

sshuser@server, echo, a, 을 얻습니다 b. 원격 명령은 echo a b, echo가 될 것 입니다 a. b인쇄됩니다 a b.

그런 다음 이:

ssh user@server 'echo a     b'

sshuser@server, 을 얻습니다 echo a b. 원격 명령은 echo a b, echo가 될 것 입니다 a. b인쇄됩니다 a b.

그리고 마지막으로 이것은:

ssh user@server 'echo "a     b"'

sshuser@server, 을 얻습니다 echo "a b". 원격 명령은 이고 echo "a b"얻을 echo수 있습니다 a b. 인쇄됩니다 a b.

결론은 로컬 쉘의 맥락에서 인용해야 한다는 것입니다.갈라져원격 쉘의 맥락에서. 셸로 항목을 확장하는 경우,외부 따옴표가 중요합니다.


해결책

이 모든 정보를 종합하고(그리고 여전히 보호하고 싶다고 가정)모든 것로컬 셸에서 확장/해석되지 않도록) 다음과 같이 조언합니다.

  • 인용하거나 탈출하십시오.
  • 선호하다인용한 쌍의 따옴표는 많은 문자를 보호할 수 있지만 단일 쌍의 따옴표는 \한 문자만 보호하기 때문에 과도한 이스케이프가 필요합니다. 모든 것을 보호하려면 많은 백슬래시가 필요할 것입니다. 한 쌍의 따옴표만으로 동일한 결과를 얻을 수 있는 경우가 많습니다.
  • 선호하다작은따옴표( '), 을 제외한 모든 것을 보호할 수 있습니다 '. 반면에 큰따옴표( ")는 보호할 수 있지만 보호할 수 는 없지만 보호 '할 수는 없습니다 ( 때때로 Bash에서는 때로는 때로는 보호할 수도 없음).$"\!$그리고인용됨, 즉 큰따옴표 내에서 이스케이프됨; 제외하고! 이는 귀찮은).
  • 명령을 제공하는 것을 선호합니다.하나의에 대한 인수 ssh.

이는 다음 절차로 이어집니다.

  1. 원격 측에서 실행하려는 축어적 명령을 준비합니다.
  2. Every를 또는로 바꾸십시오 '( '"'"'각각 '\''에 대해 독립적으로 선택할 수 있음 ').
  3. 전체 결과 문자열을 작은따옴표로 묶습니다.
  4. ssh …앞에 추가하세요 .

귀하의 축어 명령은 다음과 같습니다

sudo docker exec -it $(sudo docker ps | grep mycontainername | awk '{print $1;}') env

절차의 결과는 다음과 같습니다.

ssh -t user@server 'sudo docker exec -it $(sudo docker ps | grep mycontainername | awk '\''{print $1;}'\'') env'
# single-quoted     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^    ^^^^^^^^^^^    ^^^^^
# escaped                                                                                ^              ^

절차에 따라 가능한 가장 짧은 문자열이 나올 수도 있고 그렇지 않을 수도 있습니다. 통찰력을 통해 때때로 문자열을 "최적화"할 수 있습니다. 하지만 절차가 꽤단순한그리고 완전히 신뢰할 수 있습니다. 모든 것이 로컬 셸에 의해 확장/해석되지 않도록 보호하려는 경우 절차 자체에는 추가 통찰력이 전혀 필요하지 않습니다.


오토메이션

실제로 절차는 자동화될 수 있습니다. 문자열에 따옴표를 추가하고 기존 따옴표를 적절하게 보존할 수 있는 도구가 있습니다. Bash 자체가 그러한 도구입니다.나의 이 대답Bash에서 키 입력을 사용하여 이를 수행하는 방법을 제공합니다. 귀하의 사례에 맞게 조정 가능한 해결책은 다음과 같습니다.

  1. 로컬 셸에서 다음 사용자 정의 함수와 바인딩을 정의합니다.

     _prepend_ssh() { READLINE_LINE="ssh  ${READLINE_LINE@Q}"; READLINE_POINT=4; }
     bind -x '"\C-x\C-h":_prepend_ssh'
    
  2. 여전히 로컬 셸에서 원격 셸에서 실행하려는 명령을 입력(또는 붙여넣기)합니다. 실행하지 마십시오. 명령은 다음과 같아야합니다정확히원격 쉘에 있기를 원하는대로.

  3. Ctrl+ x, Ctrl+를 누르세요 h. 로컬 쉘이 인용을 처리합니다. 또한 ssh앞에 추가 하고 바로 뒤에 커서를 배치합니다.

  4. 누락된 인수(예: )를 추가(입력)합니다 -t user@server. 귀하의 편의를 위해 커서는 이미 이 작업을 수행할 수 있는 올바른 위치에 있습니다.

  5. Enter


대안

원격 쉘에 축어적 명령을 전달하는 또 다른 방법이 있습니다. 어떤 경우에는 다음을 수행할 수 있습니다.파이프를 통해 그들을 ssh. 원격 명령줄이 다음과 같아야 한다고 가정해 보겠습니다.

echo "$PATH"; date

위와 같이 진행하고 작은따옴표를 추가하고 다음과 같이 로컬로 실행할 수 있습니다.

ssh user@server 'echo "$PATH"; date'

예제는 간단하지만 일반적으로 따옴표를 추가하는 것이 항상 쉬운 것은 아닙니다. 또는 다음과 같이 명령을 파이프할 수 있습니다( echo단순화를 위해 첫 번째;printf더 나은):

echo 'echo "$PATH"; date' | ssh user@server bash

여전히 이러한 작은따옴표가 필요합니다. 그러나 파일에 명령이 있으면 다음을 수행하십시오.

<file ssh user@server bash

또는 파일이 없어도(여기 문서):

ssh user@server bash <<'EOF'
echo "$PATH"
date
EOF

(안의 따옴표는 로컬로 확장되는 것을 <<'EOF'방지합니다 .)$PATH

장점:

  • echo … ; date여러 줄의 명령/조각/스크립트를 쉽게 전달할 수 있습니다( 이것을 보여주기 위해 분할했습니다 ).
  • 추가 인용 계층이 필요하지 않습니다.
  • 쉘일 필요가 없는 원격 인터프리터를 명시적으로 선택할 수 있습니다(예: bashor zsh, 또는 python).

단점:

  • 원격 인터프리터를 명시적으로 지정해야 합니다. 그렇지 않으면 기본값로그인쉘이 생성되고 오늘의 메시지가 인쇄될 것입니다. 올바르게 인용하여 지정하면 기본 쉘을 비로그인 쉘로 계속 사용할 수 있습니다 exec "$SHELL"(행은 다음과 같습니다 ssh … 'exec "$SHELL"' <<'EOF').
  • 의 표준 입력은 ssh터미널이 아니므로 사용할 수 없습니다 -t(이것이 원래 명령을 예로 사용하지 않은 이유입니다).
  • 명령은 표준 입력을 통해 원격 인터프리터( bash예제에서는)에 도달합니다. 가능한 문제:
    • 하위 프로세스(또는 내장)는 동일한 표준 입력을 사용합니다. 그들 중 하나라도 stdin에서 읽으면 동일한 스트림을 읽을 것이며 아마도 인터프리터로 향하는 다음 명령을 읽을 것입니다. 이 동작은 억제되거나 창의적으로 (학대) 사용될 수도 있지만 자세히 설명하지는 않겠습니다.
    • 이 채널을 사용하여 다른 것을 쉽게 파이프할 수는 없습니다.

관련 정보