การจัดการกับการอ้างหลายระดับ (จริงๆแล้วการแยก / การตีความหลายระดับ) อาจซับซ้อน ช่วยให้ทราบบางสิ่ง:
- “ ระดับการอ้างอิง” แต่ละครั้งอาจมีภาษาต่างกัน
- กฎการอ้างอิงแตกต่างกันไปตามภาษา
- เมื่อจัดการกับระดับที่ซ้อนกันมากกว่าหนึ่งหรือสองระดับมักจะง่ายที่สุดในการทำงาน "จากด้านล่างขึ้น" (เช่นภายในสุดถึงสุด)
ระดับของการอ้างอิง
ให้เราดูคำสั่งตัวอย่างของคุณ
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ฯลฯ ) อาจต้องใช้ไวยากรณ์ที่แตกต่างกัน แต่วิธีการยังคงใช้
ล่างขึ้นบน
- กำหนดสตริงที่คุณต้องการเป็นตัวแทนในระดับที่ลึกที่สุด
- เลือกกลไกการอ้างอิงจากข้อความการอ้างอิงของภาษาสูงสุดถัดไป
- อ้างอิงสตริงที่ต้องการตามกลไกการอ้างอิงที่คุณเลือก
- มักจะมีรูปแบบมากมายที่จะใช้กลไกการอ้างอิง การทำด้วยมือมักเป็นเรื่องของการฝึกฝนและประสบการณ์ เมื่อทำการเขียนโปรแกรมโดยปกติแล้วจะเป็นการดีที่สุดที่จะเลือกสิ่งที่ง่ายที่สุดที่จะทำให้ถูกต้อง (โดยทั่วไปจะเป็น "ตัวอักษรที่สุด" (หนีน้อยที่สุด))
- เลือกใช้สตริงที่ยกมาเป็นผลลัพธ์ด้วยรหัสเพิ่มเติม
- หากคุณยังไม่ถึงระดับที่คุณต้องการในการ 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}'
และฝังไว้ในส่วนที่เหลือของเชลล์“ รหัส”:
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")"