참고 수준

참고 수준

나는 종종 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'"? 이런 상황에서 견적을 관리하는 일반적인 방법이 있나요? ...

답변1

여러 수준의 참조(실제로는 여러 수준의 구문 분석/해석)를 처리하는 것이 복잡해질 수 있습니다. 다음 사항을 염두에 두는 것이 도움이 됩니다.

  • 각 "참조 수준"에는 다양한 언어가 포함될 수 있습니다.
  • 인용 규칙은 언어마다 다릅니다.
  • 하나 또는 두 개 이상의 중첩 수준을 처리할 때는 "바닥에서 위로"(즉, 가장 안쪽 수준에서 가장 바깥쪽 수준으로) 작업하는 것이 가장 쉬운 경우가 많습니다.

참고 수준

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

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

위의 첫 번째 예제 명령은 4가지 언어, 즉 쉘, 정규식을 사용합니다.정규식, 정규식은grep(정규 표현식 언어와 다를 수 있습니다.정규식), 그리고. 관련된 해석에는 두 가지 수준이 있습니다. 쉘과 각 관련 명령에 대한 쉘 이후의 해석 수준입니다. 명시적 인용에는 한 가지 수준만 있습니다(쉘 인용).).

ssh host …

다음으로 레벨을 추가합니다.SSH위에. 이것은 실제로 또 다른 쉘 레벨입니다.SSH명령 자체는 해석되지 않고 (예를 들어) 원격 측의 쉘에 전달되며 sh -c …해당 쉘은 문자열을 해석합니다.

ssh host "sudo su user -c …"

그런 다음 다음을 사용하여 중간에 다른 쉘 레벨을 추가하는 방법을 묻습니다.(통과하다스도, 명령 인수를 해석하지 않으므로 무시할 수 있습니다). 이제 세 가지 수준의 중첩을 완료했습니다(→ 쉘, 쉘 → 쉘(SSH), 쉘 → 쉘(su 사용자 -c), 따라서 "상향식" 접근 방식을 사용하는 것이 좋습니다. 귀하의 쉘이 Bourne과 호환된다고 가정합니다(예:,금연 건강 증진 협회,스프린트,변화 많은,세게 때리다,다루기 힘든, 등. ). 다른 유형의 껍질(물고기,RC등)에는 다른 구문이 필요할 수 있지만 접근 방식은 여전히 ​​작동합니다.

상향식

  1. 가장 안쪽 수준에서 표시할 문자열을 지정합니다.
  2. 하위 수준 언어의 인용 라이브러리에서 인용 메커니즘을 선택합니다.
  3. 선택한 인용 메커니즘에 따라 원하는 문자열을 인용하세요.
    • 참조 메커니즘이 적용되는 방식에는 다양한 변형이 있는 경우가 많습니다. 손으로 만드는 작업에는 일반적으로 연습과 경험이 필요합니다. 프로그래밍 방식으로 이 작업을 수행할 때 일반적으로 가장 쉽게 올바른 것을 선택하는 것이 가장 좋습니다(보통 "가장 문자 그대로"(최소 이스케이프)).
  4. (선택 사항) 추가 코드와 함께 결과 따옴표 붙은 문자열을 사용합니다.
  5. 아직 필요한 인용/설명 수준에 도달하지 않은 경우 생성된 인용 문자열(및 추가된 코드)을 가져와 2단계의 시작 문자열로 사용하세요.

참조 의미는 다양함

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

대부분의 언어에는 "문자 그대로" 참조 메커니즘이 있지만 문자 그대로의 정도는 다양합니다. Bourne과 같은 쉘의 작은따옴표는 실제로 문자 그대로입니다(즉, 작은따옴표 문자 자체를 인용하는 데 사용할 수 없음을 의미합니다). 다른 언어(Perl, Ruby)는 해석하기 때문에 덜 간단합니다.일부작은따옴표 영역 내의 백슬래시 시퀀스는 리터럴이 아닙니다(구체적으로 \\및 은 및 \'로 이어지지 만 다른 백슬래시 시퀀스는 실제로 리터럴입니다).\'

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

당신의 모범

귀하의 예의 가장 안쪽 수준은 다음과 같습니다프로그램.

{print $1}

이것을 쉘 명령줄에 포함시키고 싶습니다:

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

(적어도) 공간과 $내부는 보호해야 해요프로그램. 확실한 선택은 쉘의 전체 프로그램 주위에 작은따옴표를 사용하는 것입니다.

  • '{print $1}'

그러나 다른 옵션도 있습니다:

  • {print\ \$1}우주에서 직접 탈출$
  • {print' $'1}작은따옴표에는 공백만 포함되며$
  • "{print \$1}"큰따옴표 전체 및 이스케이프 처리됨$
  • {print" $"1}큰따옴표에는 공백만 포함되며 $
    이로 인해 규칙이 약간 변경될 수 있지만( $큰따옴표로 묶인 문자열 끝에서 이스케이프되지 않은 것은 리터럴입니다) 대부분의 쉘에서 작동하는 것 같습니다.

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

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

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

다음으로 다음을 통해 실행하고 싶습니다.그리고스도.

sudo su user -c …

su user -c …some-shell -c …(다른 UID에서 실행하는 것을 제외하고) 마찬가지로다른 쉘 레벨을 추가하면 됩니다.스도해당 인수는 해석되지 않으므로 참조 수준을 추가하지 않습니다.

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

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

쉘은 여기에서 네 개의 문자열(첫 번째 작은따옴표로 묶인 문자열( pgrep … 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}'"'"이스케이프(1자) 대신 큰따옴표(2자)를 사용하여 평소보다 약간 길어졌습니다.

첫 번째 수준에서 다른 참조를 사용하면 이 수준에서 추가 변형이 가능합니다.

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

첫 번째 변형 삽입스도/*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 ', 이스케이프된 작은따옴표, 이스케이프된 백슬래시, 두 개의 이스케이프된 작은따옴표, 작은따옴표프로그램, 작은따옴표 이스케이프, 백슬래시 이스케이프, 마지막으로 작은따옴표 이스케이프.

최종 형태는 다음과 같습니다:

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")"

답변2

바라보다크리스 존슨의 답변명확하고 심층적인 설명과 보편적인 솔루션을 얻으세요. 몇 가지 일반적인 상황에서 도움이 될 수 있는 몇 가지 추가 팁을 제공하겠습니다.

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

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

로컬 쉘이 ksh93 또는 zsh 인 경우 변수를 '\''.${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/"/sed 대신). 따라서 -v옵션 또는 ENVIRON배열과 함께 awk를 사용하여 쉘에서 데이터를 전달합니다( ENVIRON내보내기 변수를 사용합니다).

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

답변3

~처럼Chris Johnson이 잘 설명합니다., 여기에는 여러 수준의 간접 지시가 있습니다. 로컬에 이를 통해 shell파이프라인을 실행하도록 지시해야 합니다. 이런 종류의 명령에는 지루한 작업이 많이 필요합니다.shellsshsudosushellpgrep -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>

더 알아보기:

내 (아마도 너무 장황한) 답변을 확인하세요슬래시 대체를 위한 다양한 유형의 따옴표가 있는 파이프 경로여기에서 나는 그것이 어떻게 작동하는지에 대한 몇 가지 이론을 논의합니다.

-마이크로폰

답변4

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

그렇다면 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)\"".

관련 정보