it-swarm-ko.tech

ssh $ Host $ FOO 및 ssh $ Host "Sudo su user -c $ FOO"유형 구성에서 인용

나는 종종 ssh를 통해 복잡한 명령을 내리게됩니다. 이 명령은 awk 또는 Perl 한 줄로의 파이프를 포함하며 결과적으로 작은 따옴표와 $가 포함됩니다. 인용문을 제대로 작성하기위한 어렵고 빠른 규칙을 알아낼 수 없었고 이에 대한 좋은 참고 자료도 찾지 못했습니다. 예를 들어 다음을 고려하십시오.

# what I'd run locally:
CMD='pgrep -fl Java | grep -i datanode | awk '{print $1}'
# this works with ssh $Host "$CMD":
CMD='pgrep -fl Java | grep -i datanode | awk '"'"'{print $1}'"'"

(awk 문에 추가 따옴표가 있습니다.)

그러나 이것을 어떻게 사용할 수 있습니까? ssh $Host "Sudo su user -c '$CMD'"? 그러한 시나리오에서 견적을 관리하기위한 일반적인 방법이 있습니까? ..

31
Leo Alekseyev

여러 수준의 인용 (실제로 여러 수준의 구문 분석/해석)을 처리하는 것은 복잡해질 수 있습니다. 몇 가지 사항을 염두에두면 도움이됩니다.

  • 각 "인용 수준"에는 잠재적으로 다른 언어가 포함될 수 있습니다.
  • 인용 규칙은 언어에 따라 다릅니다.
  • 하나 또는 두 개 이상의 중첩 된 수준을 처리 할 때 일반적으로 "아래에서 위로"(즉, 가장 안쪽에서 바깥쪽으로) 작업하는 것이 가장 쉽습니다.

견적 수준

예제 명령을 살펴 보겠습니다.

pgrep -fl Java | grep -i datanode | awk '{print $1}'

첫 번째 예제 명령 (위)은 셸, pgrep 의 정규식, grep의 정규식의 네 가지 언어를 사용합니다. ( pgrep ) 및 의 정규식 언어와 다를 수 있습니다. awk . 관련된 해석에는 두 가지 수준이 있습니다. 관련된 각 명령에 대해 셸과 셸 뒤의 한 수준입니다. 명시적인 인용 수준은 하나뿐입니다 ( awk 에 쉘 인용).

ssh Host …

다음으로 ssh 레벨을 맨 위에 추가했습니다. 이것은 사실상 또 다른 셸 수준입니다. ssh 명령 자체를 해석하지 않고 원격 끝의 셸에 전달합니다 (예 : sh -c …) 그 쉘이 문자열을 해석합니다.

ssh Host "Sudo su user -c …"

그런 다음 su 를 사용하여 중간에 다른 셸 수준을 추가하는 것에 대해 질문했습니다 ( Sudo , 명령 인수를 해석하지 않으므로 무시할 수 있습니다.) 이 시점에서 세 가지 수준의 중첩이 진행됩니다 ( awk → Shell, Shell → Shell ( ssh ), Shell → Shell ( su user -c ), 그래서“bottom, up”접근 방식을 사용하는 것이 좋습니다. 셸이 Bourne과 호환된다고 가정합니다 (예 : sh , ash , dash , ksh , bash , zsh 등). 다른 종류의 셸 ( fish , rc 등)에는 다른 구문이 필요할 수 있지만 방법은 여전히 ​​적용됩니다.

아래, 위로

  1. 가장 안쪽 수준에서 표현하려는 문자열을 공식화하십시오.
  2. 다음으로 높은 언어의 인용 레퍼토리에서 인용 메커니즘을 선택하십시오.
  3. 선택한 인용 메커니즘에 따라 원하는 문자열을 인용하십시오.
    • 인용 메커니즘을 적용하는 방법에는 종종 많은 변형이 있습니다. 손으로하는 것은 일반적으로 연습과 경험의 문제입니다. 프로그래밍 방식으로 수행 할 때는 일반적으로 가장 쉬운 것을 선택하는 것이 가장 좋습니다 (일반적으로 "가장 리터럴"(가장 작은 이스케이프)).
  4. 선택적으로 추가 코드와 함께 따옴표로 묶인 결과 문자열을 사용합니다.
  5. 원하는 인용/해석 수준에 아직 도달하지 않은 경우 결과 인용 문자열 (추가 된 코드 포함)을 가져와 2 단계에서 시작 문자열로 사용합니다.

인용 의미는 다양합니다.

여기서 명심해야 할 점은 각 언어 (인용 수준)가 동일한 인용 문자에 대해 약간 다른 의미 (또는 매우 다른 의미)를 제공 할 수 있다는 것입니다.

대부분의 언어에는 "문자 적"인용 메커니즘이 있지만 문자 그대로의 방식은 다릅니다. Bourne과 같은 쉘의 작은 따옴표는 실제로 리터럴입니다 (즉, 작은 따옴표 문자 자체를 인용하는 데 사용할 수 없음을 의미합니다). 다른 언어 (Perl, Ruby)는 some 작은 따옴표 영역 내의 백 슬래시 시퀀스를 문자 그대로 해석한다는 점에서 덜 문자 적입니다 (특히 \\\' 결과 \'에 있지만 다른 백 슬래시 시퀀스는 실제로 리터럴입니다.).

인용 규칙과 전체 구문을 이해하려면 각 언어에 대한 설명서를 읽어야합니다.

귀하의 예

예제의 가장 안쪽 수준은 awk 프로그램입니다.

{print $1}

이것을 셸 명령 줄에 포함 할 것입니다.

pgrep -fl Java | grep -i datanode | awk …

awk 프로그램에서 (최소한) 공간과 $를 보호해야합니다. 명백한 선택은 전체 프로그램 주위에 셸에서 작은 따옴표를 사용하는 것입니다.

  • '{print $1}'

하지만 다른 선택이 있습니다.

  • {print\ \$1} 공백을 직접 이스케이프하고 $
  • {print' $'1} 작은 따옴표 만 공백 및 $
  • "{print \$1}" 전체를 큰 따옴표로 묶고 $
  • {print" $"1} 공백과 $ 만 큰 따옴표로 묶으세요.
    이것은 규칙을 약간 구부릴 수 있지만 (큰 따옴표로 묶인 문자열의 끝에있는 이스케이프 처리되지 않은 $는 리터럴 임) 대부분의 셸에서 작동하는 것 같습니다.

프로그램이 여는 중괄호와 닫는 중괄호 사이에 쉼표를 사용하는 경우 일부 쉘에서 "중괄호 확장"을 방지하기 위해 쉼표 또는 중괄호를 인용하거나 이스케이프해야합니다.

'{print $1}'를 선택하고 나머지 셸 "코드"에 포함합니다.

pgrep -fl Java | grep -i datanode | awk '{print $1}'

다음으로 su Sudo 를 통해 실행하려고했습니다.

Sudo su user -c …

su user -c …some-Shell -c …와 유사합니다 (다른 UID에서 실행되는 경우 제외). 따라서 su 는 다른 셸 수준을 추가합니다. Sudo 는 인수를 해석하지 않으므로 인용 수준을 추가하지 않습니다.

명령 문자열에 대해 다른 셸 수준이 필요합니다. 작은 따옴표를 다시 선택할 수 있지만 기존의 작은 따옴표를 특별히 처리해야합니다. 일반적인 방법은 다음과 같습니다.

'pgrep -fl Java | grep -i datanode | awk '\''{print $1}'\'

여기에 쉘이 해석하고 연결할 네 개의 문자열이 있습니다. 첫 번째 작은 따옴표 문자열 (pgrep … awk), 이스케이프 된 작은 따옴표, 작은 따옴표 awk 프로그램, 또 다른 이스케이프 된 작은 따옴표.

물론 많은 대안이 있습니다.

  • pgrep\ -fl\ Java\ \|\ grep\ -i\ datanode\ \|\ awk\ \'{print\ \$1} 중요한 모든 것을 이스케이프
  • pgrep\ -fl\ Java\|grep\ -i\ datanode\|awk\ \'{print\$1} 동일하지만 불필요한 공백이 없습니다 ( awk 프로그램에서도!)
  • "pgrep -fl Java | grep -i datanode | awk '{print \$1}'" 전체를 큰 따옴표로 묶고 $
  • 'pgrep -fl Java | grep -i datanode | awk '"'"'{print \$1}'"'" 유사 콘텐츠; 이스케이프 (한 문자) 대신 큰 따옴표 (두 문자)를 사용하기 때문에 일반적인 방법보다 약간 더 깁니다.

첫 번째 수준에서 다른 인용을 사용하면이 수준에서 다른 변형이 가능합니다.

  • 'pgrep -fl Java | grep -i datanode | awk "{print \$1}"'
  • 'pgrep -fl Java | grep -i datanode | awk {print\ \$1}'

Sudo /* su * 명령 줄에 첫 번째 변형을 포함하면 다음이 제공됩니다.

Sudo su user -c 'pgrep -fl Java | grep -i datanode | awk '\''{print $1}'\'

다른 단일 셸 수준 컨텍스트 (예 : ssh Host …)에서 동일한 문자열을 사용할 수 있습니다.

다음으로 ssh 수준을 맨 위에 추가했습니다. 이것은 사실상 또 다른 셸 수준입니다. ssh 명령 자체를 해석하지 않지만 원격 끝의 셸에 전달합니다 (예 : sh -c …) 그 쉘이 문자열을 해석합니다.

ssh Host …

프로세스는 동일합니다. 문자열을 가져 와서 인용 방법을 선택하고, 사용하고, 삽입합니다.

다시 작은 따옴표 사용 :

'Sudo su user -c '\''pgrep -fl Java | grep -i datanode | awk '\'\\\'\''{print $1}'\'\\\'

이제 해석되고 연결되는 11 개의 문자열이 있습니다. 'Sudo su user -c ', 이스케이프 된 작은 따옴표, 'pgrep … awk ', 이스케이프 된 작은 따옴표, 이스케이프 된 백 슬래시, 이스케이프 된 두 개의 작은 따옴표, 작은 따옴표 awk 프로그램, 이스케이프 된 작은 따옴표, 이스케이프 된 백 슬래시 및 마지막 이스케이프 된 작은 따옴표.

최종 양식은 다음과 같습니다.

ssh Host 'Sudo su user -c '\''pgrep -fl Java | grep -i datanode | awk '\'\\\'\''{print $1}'\'\\\'

이것은 손으로 입력하기가 약간 어렵지만 Shell의 단일 인용의 문자 적 ​​특성으로 인해 약간의 변형을 쉽게 자동화 할 수 있습니다.

#!/bin/sh

sq() { # single quote for Bourne Shell evaluation
    # Change ' to '\'' and wrap in single quotes.
    # If original starts/ends with a single quote, creates useless
    # (but harmless) '' at beginning/end of result.
    printf '%s\n' "$*" | sed -e "s/'/'\\\\''/g" -e 1s/^/\'/ -e \$s/\$/\'/
}

# Some shells (ksh, bash, zsh) can do something similar with %q, but
# the result may not be compatible with other shells (ksh uses $'...',
# but dash does not recognize it).
#
# sq() { printf %q "$*"; }

ap='{print $1}'
s1="pgrep -fl Java | grep -i datanode | awk $(sq "$ap")"
s2="Sudo su user -c $(sq "$s1")"

ssh Host "$(sq "$s2")"
35
Chris Johnsen

일반적인 솔루션에 대한 명확하고 심층적 인 설명은 Chris Johnsen의 답변 을 참조하십시오. 몇 가지 일반적인 상황에서 도움이되는 몇 가지 추가 팁을 제공하겠습니다.

작은 따옴표는 작은 따옴표를 제외한 모든 것을 이스케이프합니다. 따라서 변수 값에 작은 따옴표가 포함되어 있지 않다는 것을 알고 있다면 Shell 스크립트에서 작은 따옴표 사이에 안전하게 보간 할 수 있습니다.

su -c "grep '$pattern' /root/file"  # assuming there is no ' in $pattern

로컬 셸이 ksh93 또는 zsh 인 경우 변수의 작은 따옴표를 '\''로 다시 작성하여 처리 할 수 ​​있습니다. (bash에도 ${foo//pattern/replacement} 구조가 있지만 작은 따옴표 처리는 이해가되지 않습니다.)

su -c "grep '${pattern//'/'\''}' /root/file"  # if the outer Shell is zsh
su -c "grep '${pattern//\'/\'\\\'\'}' /root/file"  # if the outer Shell is ksh93

중첩 된 인용을 처리 할 필요가없는 또 다른 팁은 가능한 한 환경 변수를 통해 문자열을 전달하는 것입니다. Ssh 및 Sudo는 대부분의 환경 변수를 삭제하는 경향이 있지만 일반적으로 유용성에 매우 중요하고 (로케일 정보가 포함되어 있음) 보안에 민감한 것으로 간주되지 않기 때문에 LC_*를 통과하도록 구성되는 경우가 많습니다.

LC_CMD='what you would use locally' ssh $Host 'Sudo su user -c "$LC_CMD"'

여기서 LC_CMD에는 Shell 조각이 포함되어 있으므로 문자 그대로 가장 안쪽의 Shell에 제공해야합니다. 따라서 변수는 바로 위에있는 셸에 의해 확장됩니다. 가장 안쪽에있는 쉘은 "$LC_CMD"를보고 가장 안쪽에있는 쉘은 명령을 볼 수 있습니다.

유사한 방법이 데이터를 텍스트 처리 유틸리티에 전달하는 데 유용합니다. 셸 보간을 사용하는 경우 유틸리티는 변수 값을 명령으로 처리합니다. 변수에 sed "s/$pattern/$replacement/"가 포함되어 있으면 /가 작동하지 않습니다. 따라서 awk (sed 아님)와 -v 옵션 또는 ENVIRON 배열을 사용하여 셸에서 데이터를 전달합니다 (ENVIRON를 통과하는 경우 변수를 내보내는 것을 잊지 마십시오) ).

awk -vpattern="$pattern" replacement="$replacement" '{gsub(pattern,replacement); print}'

Chris Johnson이 아주 잘 설명했듯이 , 여기에 몇 가지 수준의 간접 참조가 있습니다. 로컬 ShellShell을 통해 원격 ssh에 지시하여 Sudosu에 파이프 라인 pgrep -fl Java | grep -i datanode | awk '{print $1}'Shell로 실행하도록 지시하도록 user에 지시해야한다고 지시합니다. 이런 종류의 명령에는 많은 지루한 \'"quote quoting"\'가 필요합니다.

내 충고를 받아들이면 말도 안되는 모든 것을 포기하고 다음을 수행합니다.

% ssh $Host <<REM=LOC_EXPANSION <<'REMOTE_CMD' |
> localhost_data='$(commands run on localhost at runtime)' #quotes don't affect expansion
> more_localhost_data="$(values set at heredoc expansion)" #remote Shell will receive m_h_d="result"
> REM=LOC_EXPANSION
> commands typed exactly as if located at 
> the remote terminal including variable 
> "${{more_,}localhost_data}" operations
> 'quotes and' \all possibly even 
> a\wk <<'REMOTELY_INVOKED_HEREDOC' |
> {as is often useful with $awk
> so long as the terminator for}
> REMOTELY_INVOKED_HEREDOC
> differs from that of REM=LOC_EXPANSION and
> REMOTE_CMD
> and here you can | pipeline operate on |\
> any output | received from | ssh as |\
> run above | in your local | terminal |\
> however | tee > ./you_wish.result
<desired output>

더보기 :

슬래시 대체에 대한 다른 유형의 따옴표가있는 파이프 경로 에 대한 내 (아마도 너무 긴) 대답을 확인하십시오.

-마이크

2
mikeserv

더 많은 큰 따옴표를 사용하는 것은 어떻습니까?

그러면 ssh $Host $CMD가 다음과 같이 잘 작동합니다.

CMD="pgrep -fl Java | grep -i datanode | awk '{print $1}'"

이제 더 복잡한 ssh $Host "Sudo su user -c \"$CMD\""입니다. CMD : $, \"에서 민감한 문자를 이스케이프하기 만하면됩니다. 그래서 이것이 작동하는지 확인하려고합니다 : echo $CMD | sed -e 's/[$\\"]/\\\1/g'.

괜찮아 보이면 echo + sed를 Shell 함수로 감싸고 ssh $Host "Sudo su user -c \"$(escape_my_var $CMD)\""을 사용하는 것이 좋습니다.

0
alex