ssh $ host $ FOO 및 ssh $ host“sudo su user -c $ FOO”유형 구문에서 인용


30

나는 종종 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'"합니까? 이러한 시나리오에서 견적을 관리하는 일반적인 방법이 있습니까? ..

답변:


35

여러 수준의 인용 (실제로 여러 수준의 파싱 / 통역)을 다루는 것은 복잡 할 수 있습니다. 몇 가지 사항을 명심하십시오.

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

인용 수준

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

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과 같은 쉘의 작은 따옴표는 실제로 리터럴입니다 (따라서 작은 따옴표 문자 자체를 인용하는 데 사용할 수 없음). 다른 언어 (펄, 루비) 이하가 해석하는 것을 문자 그대로입니다 일부 가 아닌 문자 그대로 하나의 인용 지역 내부의 백 슬래시 시퀀스를 (특히, \\\'에서 결과 \'하지만, 다른 백 슬래시 시퀀스는 실제로 문자입니다).

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

당신의 예

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

{print $1}

이것을 쉘 명령 행에 포함 시키려고합니다 :

pgrep -fl java | grep -i datanode | awk 

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

  • '{print $1}'

그래도 다른 선택이 있습니다.

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

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

우리는 '{print $1}'그것을 나머지 쉘“code”에 골라 넣습니다.

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

다음으로 susudo 를 통해 이것을 실행하고 싶었습니다 .

sudo su user -c 

su user -c …some-shell -c …다른 UID로 실행되는 것을 제외하고 는 su 와 비슷 하므로 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}'"'"당신의 변형; 이스케이프 대신 큰 따옴표 (2 문자)를 사용하여 일반적인 방법보다 약간 길다 (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}'\'\\\'

이것은 손으로 타이핑하기에는 다소 까다 롭지 만 쉘의 작은 따옴표는 문자 그대로의 특성으로 약간의 변형을 쉽게 자동화 할 수 있습니다.

#!/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")"

5
좋은 설명!
Gilles 'SO- 악마 중지

7

일반적인 해결책으로 명확하고 심도있는 설명을 보려면 Chris Johnsen의 답변 을 참조하십시오 . 몇 가지 일반적인 상황에서 도움이되는 몇 가지 추가 팁을 제공하겠습니다.

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

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쉘 스 니펫을 포함하므로 문자 그대로 가장 안쪽 쉘에 제공해야합니다. 따라서 변수는 바로 위의 쉘에 의해 확장됩니다. 가장 안쪽이지만 하나 인 쉘은을보고 "$LC_CMD", 가장 안쪽 쉘은 명령을 봅니다.

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

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

2

Chris Johnson이 잘 설명 했듯이 여기에 인용 된 간접적 인 수준이 몇 가지 있습니다. 해당 지역의 지시합니다 당신은 shell원격 지시하기 shell를 통해 ssh그것을 지시 할 것을 sudo지시 su(가) 원격 지시하는 shell파이프 라인 실행 pgrep -fl java | grep -i datanode | awk '{print $1}'등을 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>

이상:

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

-마이크


이것은 흥미로워 보이지만 작동시킬 수는 없습니다. 최소한의 실례를 게시 할 수 있습니까?
John Lawrence Aspden

이 예제에는 stdin에 여러 리디렉션이 사용되므로 zsh가 필요하다고 생각합니다. 다른 Bourne과 같은 쉘에서는 두 번째 쉘 <<이 첫 번째 쉘을 대체합니다. 어딘가에 "zsh only"라고 말해야합니까, 아니면 뭔가 빠졌습니까? (그러나 일부 지역 확장의 대상이되는

다음은 bash 호환 버전입니다 : unix.stackexchange.com/questions/422489/…
dabest1

0

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

그렇다면 당신 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를 쉘 함수로 감싸십시오 ssh $host "sudo su user -c \"$(escape_my_var $CMD)\"".

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.