การอ้างถึง ssh $ host $ FOO และ ssh $ host“ sudo su user -c $ FOO”


30

ฉันมักจะจบลงด้วยการออกคำสั่งที่ซับซ้อนมากกว่า ssh; คำสั่งเหล่านี้เกี่ยวข้องกับ piping to awk หรือ perl one-line และด้วยเหตุนี้จึงมีการเสนอราคาเดียวและ $ 's ฉันไม่สามารถคิดออกกฎอย่างหนักและรวดเร็วในการอ้างอิงได้อย่างถูกต้องและไม่พบการอ้างอิงที่ดีสำหรับมัน ตัวอย่างเช่นพิจารณาสิ่งต่อไปนี้:

# 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}'

คำสั่งตัวอย่างแรกของคุณ (ด้านบน) ใช้สี่ภาษา: เชลล์ของคุณ, regex ในpgrep , regex in grep (ซึ่งอาจแตกต่างจากภาษา regex ใน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 , ฯลฯ ) เชลล์ประเภทอื่น ๆ ( ปลา , rcฯลฯ ) อาจต้องใช้ไวยากรณ์ที่แตกต่างกัน แต่วิธีการยังคงใช้

ล่างขึ้นบน

  1. กำหนดสตริงที่คุณต้องการเป็นตัวแทนในระดับที่ลึกที่สุด
  2. เลือกกลไกการอ้างอิงจากข้อความการอ้างอิงของภาษาสูงสุดถัดไป
  3. อ้างอิงสตริงที่ต้องการตามกลไกการอ้างอิงที่คุณเลือก
    • มักจะมีรูปแบบมากมายที่จะใช้กลไกการอ้างอิง การทำด้วยมือมักเป็นเรื่องของการฝึกฝนและประสบการณ์ เมื่อทำการเขียนโปรแกรมโดยปกติแล้วจะเป็นการดีที่สุดที่จะเลือกสิ่งที่ง่ายที่สุดที่จะทำให้ถูกต้อง (โดยทั่วไปจะเป็น "ตัวอักษรที่สุด" (หนีน้อยที่สุด))
  4. เลือกใช้สตริงที่ยกมาเป็นผลลัพธ์ด้วยรหัสเพิ่มเติม
  5. หากคุณยังไม่ถึงระดับที่คุณต้องการในการ quoting / ตีความใช้สตริงที่ยกมาเป็นผล (บวกรหัสเพิ่มใด ๆ ) และใช้มันเป็นสตริงเริ่มต้นในขั้นตอนที่ 2

การอ้างอิงความหมายต่างกันไป

สิ่งที่ควรทราบคือภาษาแต่ละภาษา (ระดับการอ้างอิง) อาจให้ความหมายที่แตกต่างกันเล็กน้อย (หรือแม้แต่ความหมายที่แตกต่างกันอย่างมาก) ไปยังตัวละครเดียวกัน

ภาษาส่วนใหญ่มีกลไกการอ้างอิงตามตัวอักษร แต่แตกต่างกันไปตามที่แท้จริง คำพูดเดียวของเชลล์คล้ายบอร์นนั้นแท้จริงแล้ว (ซึ่งหมายความว่าคุณไม่สามารถใช้มันเพื่ออ้างถึงอักขระเครื่องหมายคำพูดเดี่ยวได้) ภาษาอื่น ๆ (Perl, Ruby) มีความหมายน้อยกว่าโดยที่พวกเขาตีความลำดับแบ็กสแลชบางส่วนภายในขอบเขตที่อ้างถึงเดี่ยวซึ่งไม่ใช่ตัวอักษร (โดยเฉพาะ\\และ\'ส่งผลให้\และ'แต่ส่วนแบ็กสแลชอื่น ๆ นั้นแท้จริงแล้ว)

คุณจะต้องอ่านเอกสารประกอบสำหรับแต่ละภาษาของคุณเพื่อทำความเข้าใจกฎการอ้างอิงและไวยากรณ์โดยรวม

ตัวอย่างของคุณ

ตัวอย่างด้านในสุดของคุณคือโปรแกรม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}'"'"รูปแบบของคุณ ยาวกว่าวิธีปกติเล็กน้อยเนื่องจากใช้เครื่องหมายคำพูดคู่ (อักขระสองตัว) แทนการ Escape (อักขระหนึ่งตัว)

การใช้ข้อความที่แตกต่างในระดับแรกจะช่วยให้รูปแบบอื่น ๆ ในระดับนี้:

  • '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}'\'\\\'

ขณะนี้มีสิบเอ็ดสตริงที่ถูกตีความและต่อกัน: '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 'หยุดความชั่วร้าย'

7

ดูคำตอบของ Chris Johnsenสำหรับคำอธิบายที่ชัดเจนและเจาะลึกด้วยโซลูชันทั่วไป ฉันจะให้คำแนะนำพิเศษบางอย่างที่ช่วยในสถานการณ์ทั่วไป

คำพูดเดี่ยวหนีทุกอย่างยกเว้นคำพูดเดียว ดังนั้นถ้าคุณรู้ว่าค่าของตัวแปรไม่รวมเครื่องหมายคำพูดเดี่ยวใด ๆ คุณสามารถแก้ไขได้อย่างปลอดภัยระหว่างเครื่องหมายคำพูดเดี่ยวในเชลล์สคริปต์

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/" /ดังนั้นใช้ awk (ไม่ใช่ sed) และ-vเลือกหรือENVIRONอาร์เรย์เพื่อส่งผ่านข้อมูลจากเชลล์ (ถ้าคุณผ่านไปENVIRONอย่าลืมส่งออกตัวแปร)

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

2

ในฐานะที่คริสจอห์นสันอธิบายได้ดีคุณมีระดับการอ้างอิงทางอ้อมที่นี่ คุณสั่งท้องถิ่นของคุณ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>

เพื่อเพิ่มเติม:

ตรวจสอบคำตอบของฉัน (อาจยาวเกินไป) เพื่อตอบสนองต่อPiping Path ด้วยการเสนอราคาแบบต่างๆสำหรับการทดแทนสแลชซึ่งฉันพูดถึงทฤษฎีบางอย่างที่อยู่เบื้องหลังว่าทำไมมันถึงได้ผล

-Mike


สิ่งนี้ดูน่าสนใจ แต่ฉันไม่สามารถทำงานได้ คุณช่วยโพสต์ตัวอย่างการทำงานขั้นต่ำได้ไหม
John Lawrence Aspden

ฉันเชื่อว่าตัวอย่างนี้ต้องใช้ zsh เนื่องจากใช้การเปลี่ยนเส้นทางหลายครั้งเพื่อ stdin ในเชลล์คล้ายบอร์นอื่น ๆ ที่สอง<<จะแทนที่เชลล์แรก มันควรจะพูดว่า "zsh only" ที่ไหนสักแห่งหรือฉันพลาดบางสิ่งบางอย่าง? (เคล็ดลับที่ฉลาดเพื่อให้มี heredoc ที่บางส่วนอาจมีการขยายตัวในท้องถิ่น)

นี่เป็นเวอร์ชั่นที่ใช้งานร่วมกันได้ของ 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.