
나는 종종 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등)에는 다른 구문이 필요할 수 있지만 접근 방식은 여전히 작동합니다.
상향식
- 가장 안쪽 수준에서 표시할 문자열을 지정합니다.
- 하위 수준 언어의 인용 라이브러리에서 인용 메커니즘을 선택합니다.
- 선택한 인용 메커니즘에 따라 원하는 문자열을 인용하세요.
- 참조 메커니즘이 적용되는 방식에는 다양한 변형이 있는 경우가 많습니다. 손으로 만드는 작업에는 일반적으로 연습과 경험이 필요합니다. 프로그래밍 방식으로 이 작업을 수행할 때 일반적으로 가장 쉽게 올바른 것을 선택하는 것이 가장 좋습니다(보통 "가장 문자 그대로"(최소 이스케이프)).
- (선택 사항) 추가 코드와 함께 결과 따옴표 붙은 문자열을 사용합니다.
- 아직 필요한 인용/설명 수준에 도달하지 않은 경우 생성된 인용 문자열(및 추가된 코드)을 가져와 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
파이프라인을 실행하도록 지시해야 합니다. 이런 종류의 명령에는 지루한 작업이 많이 필요합니다.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>
더 알아보기:
내 (아마도 너무 장황한) 답변을 확인하세요슬래시 대체를 위한 다양한 유형의 따옴표가 있는 파이프 경로여기에서 나는 그것이 어떻게 작동하는지에 대한 몇 가지 이론을 논의합니다.
-마이크로폰
답변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)\""
.