ฉันจะอ้างอิงการขยายตัวแปรภายในสตริงเพื่อหลีกเลี่ยงการแยกคำได้อย่างไร


9
$ myvar="/path to/my directory"
$ sudo bash -c "cd $myvar"

ในกรณีเช่นนี้ฉัน$myvarจะอ้างอิงเพื่อหลีกเลี่ยงการแยกคำเนื่องจากช่องว่างสีขาวในค่าของได้myvarอย่างไร


4
ไม่ว่าคุณจะแก้ปัญหาอย่างไรก็ยังคงไม่ทำอะไรมาก cdมีเพียงผลภายในbash -cเปลือก
Kusalananda

ฉันให้เพียงตัวอย่างน้อยที่สุดสำหรับคำถามเท่านั้นไม่ใช่วิธีแก้ปัญหาที่ใช้งานได้จริงซึ่งเป็นunix.stackexchange.com/a/269080/674บวกกับในสตริงคำสั่งที่กำหนดให้sudo bash -cฉันมีการขยายตัวแปร ชื่อพา ธ ซึ่งอาจมีช่องว่าง
ทิม

คำตอบ:


13

ไม่มีการแยกคำ (ในคุณลักษณะที่แยกตัวแปรตามส่วนขยายที่$myvarไม่ได้กล่าวถึง) ในโค้ดนั้นเนื่องจากไม่ได้ถูกอ้างอิง

มี แต่ช่องโหว่ฉีดคำสั่งเป็นขยายตัวก่อนที่จะถูกส่งผ่านไปยัง$myvar bashดังนั้นเนื้อหาจะถูกตีความเป็นรหัสทุบตี!

ช่องว่างในนั้นจะทำให้เกิดการขัดแย้งหลายประการที่จะถูกส่งผ่านไปcdไม่ใช่เพราะการแยกคำแต่เพราะพวกเขาจะได้รับการแยกเป็นโทเค็นหลาย ๆ ในไวยากรณ์เปลือก ด้วยค่าbye;rebootที่จะรีบูต! ¹

ที่นี่คุณต้องการ:

sudo bash -c 'cd -P -- "$1"' bash "$myvar"

(ที่คุณส่งเนื้อหาของ$myvarเป็นอาร์กิวเมนต์แรกของสคริปต์แบบอินไลน์นั้นให้สังเกตว่าทั้งคู่$myvarและ$1ถูกอ้างถึงสำหรับเชลล์ที่เกี่ยวข้องเพื่อป้องกัน IFS-word-splitting (และ globbing) อย่างไร

หรือ:

sudo MYVAR="$myvar" bash -c 'cd -P -- "$MYVAR"'

(ที่คุณส่งเนื้อหาของ$myvarในตัวแปรสภาพแวดล้อม)

แน่นอนคุณจะไม่บรรลุอะไรที่เป็นประโยชน์โดยการเรียกใช้เฉพาะ cdในสคริปต์แบบอินไลน์ที่ (นอกเหนือจากการตรวจสอบว่าrootสามารถcdเข้าไปที่นั่น) สมมุติว่าคุณต้องการให้สคริปต์cdนั้นอยู่ที่นั่นแล้วทำอย่างอื่นที่นั่นเช่น:

sudo bash -c 'cd -P -- "$1" && do-something' bash "$myvar"

หากความตั้งใจที่จะใช้sudoเพื่อให้สามารถcdเข้าไปในไดเรกทอรีที่คุณไม่สามารถเข้าถึงได้แสดงว่าไม่สามารถใช้งานได้จริง

sudo sh -c 'cd -P -- "$1" && exec bash' sh "$myvar"

จะเริ่มต้นการโต้ตอบกับไดเรกทอรีปัจจุบันในbash แต่เปลือกที่จะทำงานตามที่$myvarroot

คุณสามารถทำได้:

sudo sh -c 'cd -P -- "$1" && exec sudo -u "$SUDO_USER" bash' sh "$myvar"

ในการรับการโต้ตอบแบบไม่มีสิทธิพิเศษbashกับไดเรกทอรีปัจจุบัน$myvarแต่ถ้าคุณไม่มีสิทธิ์cdในไดเรกทอรีนั้นในตอนแรกคุณจะไม่สามารถทำสิ่งใดในไดเรกทอรีนั้นแม้ว่าจะเป็นไดเรกทอรีทำงานปัจจุบันของคุณ

$ myvar=/var/spool/cron/crontabs
$ sudo sh -c 'cd -P -- "$1" && exec sudo -u "$SUDO_USER" bash' sh "$myvar"
bash-4.4$ ls
ls: cannot open directory '.': Permission denied

ข้อยกเว้นจะเกิดขึ้นหากคุณมีสิทธิ์ในการค้นหาในไดเรกทอรี แต่จะไม่รวมอยู่ในองค์ประกอบไดเรกทอรีของเส้นทาง:

$ myvar=1/2
$ mkdir -p "$myvar"
$ chmod 0 1
$ cd 1/2
cd: permission denied: 1/2
$ sudo sh -c 'cd -P -- "$1" && exec sudo -u "$SUDO_USER" bash' sh "$myvar"
bash-4.4$ pwd
/home/stephane/1/2
bash-4.4$ mkdir 3
bash-4.4$ ls
3
bash-4.4$ cd "$PWD"
bash: cd: /home/stephane/1/2: Permission denied

speaking การพูดอย่างเคร่งครัดสำหรับค่าของ$myvarlike $(seq 10)(ตามตัวอักษร) จะมีการแยกคำแน่นอนเมื่อการขยายตัวของการทดแทนคำสั่งนั้นโดยbash เชลล์เริ่มต้นเป็นroot


4

มีเวทย์มนต์ใหม่ (bash 4.4) ที่ให้คุณทำสิ่งที่คุณต้องการได้โดยตรง ขึ้นอยู่กับบริบทที่ใหญ่กว่าการใช้หนึ่งในเทคนิคอื่นอาจดีกว่า แต่มันจะทำงานในบริบทที่ จำกัด นี้

sudo bash -c "cd ${myvar@Q}; pwd"

4

หากคุณไม่สามารถเชื่อถือเนื้อหาของ$myvarวิธีการที่เหมาะสมเพียงอย่างเดียวคือการใช้ GNU printfเพื่อสร้างรุ่นที่หลบหนี:

#!/bin/bash

myvar=$(printf '%q' "$myvar")
bash -c "echo $myvar"

ในฐานะที่เป็นprintfหน้าคนพูดว่า:

%q

ARGUMENT ถูกพิมพ์ในรูปแบบที่สามารถนำกลับมาใช้ใหม่เป็นอินพุตของเชลล์ได้โดยหลีกเลี่ยงอักขระที่ไม่สามารถพิมพ์ได้ด้วย$''ไวยากรณ์POSIX ที่เสนอ


ดูคำตอบของฉันสำหรับรุ่นพกพานี้
.. GitHub หยุดช่วย ICE

GNU printfจะถูกเรียกใช้บนระบบ GNU เท่านั้นและหาก$(printf '%q' "$myvar")เรียกใช้ในเชลล์ที่printfไม่ได้สร้างขึ้น หอยส่วนใหญ่มีอยู่printfในปัจจุบัน ไม่ได้ทั้งหมดสนับสนุนและแม้ว่าเมื่อพวกเขาทำพวกเขาจะพูดสิ่งที่อยู่ในรูปแบบการสนับสนุนจากเปลือกที่สอดคล้องกันซึ่งอาจไม่จำเป็นต้องเป็นเช่นเดียวกับที่เข้าใจ%q bashอันที่จริงbash's printfล้มเหลวในการพูดสิ่งที่ถูกต้องสำหรับแม้แต่ตัวเองสำหรับค่าของบางส่วน$myvarในบางสถานที่
Stéphane Chazelas

ด้วยbash-4.3อย่างน้อยที่ยังคงเป็นช่องโหว่ในการฉีดคำสั่งที่มีmyvar=$'\xa3``reboot`\xa3`' อยู่ในสถานที่เกิดเหตุ zh_HK.big5hkscs เช่น
Stéphane Chazelas

3

ก่อนอื่นอย่างที่คนอื่นสังเกตเห็นว่าcdคำสั่งของคุณไม่มีประโยชน์เพราะมันเกิดขึ้นในบริบทของเชลล์ที่ออกไปทันที แต่โดยทั่วไปแล้วคำถามนี้สมเหตุสมผลดี สิ่งที่คุณต้องการคือวิธีที่เชลล์อ้างถึงสตริงโดยพลการเพื่อให้สามารถใช้ในบริบทที่มันจะถูกตีความว่าเป็นสตริงที่อ้างถึงเชลล์ ฉันมีตัวอย่างนี้ในหน้าshของฉัน:

quote () { printf %s\\n "$1" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/'/" ; }

ด้วยฟังก์ชั่นนี้คุณสามารถทำได้:

myvar="/path to/my directory"
sudo bash -c "cd $(quote "$myvar")"

3

สิ่งที่Stéphane Chazelas แนะนำนั้นเป็นวิธีที่ดีที่สุดในการทำสิ่งนี้ ฉันจะให้คำตอบเกี่ยวกับวิธีอ้างอิงอย่างปลอดภัยด้วยไวยากรณ์การขยายพารามิเตอร์เมื่อเทียบกับการใช้sedกระบวนการย่อยเช่นในคำตอบของร ..

myvar='foo \'\''"bar'

quote() {
  printf "'%s'" "${1//\'/\'\\\'\'}"
}

printf "value: %s\n" "$myvar"
printf "quoted: %s\n" "$(quote "$myvar")"
bash -c "printf 'interpolated in subshell script: %s\n' $(quote "$myvar")"

ผลลัพธ์ของสคริปต์นั้นคือ:

value: foo \'"bar
quoted: 'foo \'\''"bar'
interpolated in subshell script: foo \'"bar

1

ใช่มีการแบ่งคำ
ในทางเทคนิคแล้วการแยกบรรทัดคำสั่งบน bash จะเป็นการแยกบรรทัด

ก่อนอื่นมาขอความถูกต้อง:

วิธีที่ง่ายที่สุด (และไม่ปลอดภัย) ของโซลูชันในการรับคำสั่งให้ทำงานตามที่ฉันเชื่อว่าคุณจะต้องการคือ:

bash -c "cd \"$myvar\""

เรามายืนยันสิ่งที่เกิดขึ้น (สมมติว่าmyvar="/path to/my directory"):

$ bash -c "printf '<%s>\n' $myvar"
<./path>
<to/my>
<directory>

อย่างที่คุณเห็นสตริงพา ธ ถูกแบ่งบนช่องว่าง และ:

$ bash -c "printf '<%s>\n' \"$myvar\""
<./path to/my directory>

ไม่แยก

อย่างไรก็ตามคำสั่ง (ตามที่เขียน) ไม่ปลอดภัย ใช้ดีกว่า:

$ bash -c "cd -P -- \"$myvar\"; pwd"
/home/user/temp/path to/my directory

ที่จะป้องกันซีดีจากไฟล์ที่ขึ้นต้นด้วยเส้นประ (-) หรือตามลิงค์ที่อาจนำไปสู่ปัญหา

สิ่งนี้จะใช้ได้หากคุณสามารถเชื่อถือได้ว่าสตริงใน$myvarนั้นเป็นเส้นทาง
อย่างไรก็ตาม "การฉีดรหัส" ยังคงเป็นไปได้เนื่องจากคุณ "กำลังเรียกใช้งานสตริง":

$ myvar='./path to/my directory";date;"true'
$ bash -c "printf '<%s> ' \"$myvar\"; echo"
<./path to/my directory> Tue May  8 12:25:51 UTC 2018

คุณต้องการข้อความที่แข็งแกร่งกว่าเช่น:

$ bash -c 'printf "<%s> " "$1"; echo' _ "$myvar"
<./path to/my directory";date -u;"true>

ขณะที่คุณสามารถดูด้านบนค่าไม่ได้ตีความ "$1'แต่เพียงใช้เป็น

ตอนนี้เราสามารถใช้ sudo ได้อย่างปลอดภัย

$ sudo bash -c 'cd -P -- "$1"; pwd' _ "$myvar"
_: line 0: cd: ./path to/my directory";date -u;"true: No such file or directory

หากมีหลายข้อโต้แย้งคุณสามารถใช้:

$ sudo bash -c 'printf "<%s>" "$@"' _ "$val1" "$val2" "$val3"

-2

เครื่องหมายคำพูดเดี่ยวจะไม่ทำงานที่นี่เนื่องจากตัวแปรของคุณจะไม่ถูกขยาย หากคุณมั่นใจว่าตัวแปรสำหรับเส้นทางของคุณถูกทำให้สะอาดโซลูชั่นที่ง่ายที่สุดคือการเพิ่มเครื่องหมายคำพูดรอบ ๆ การขยายตัวที่เกิดขึ้นทันทีที่มันอยู่ใน bash shell ใหม่:

sudo bash -c "cd \"$myvar\""

2
ยังคงเป็นช่องโหว่การฉีดคำสั่งเช่นค่า$myvarชอบ$(reboot)หรือ"; reboot; #
Stéphane Chazelas

1
การใช้sudo bash -c "cd -P -- '$myvar'"จะ จำกัด ปัญหาเฉพาะค่า$myvar ที่มีอักขระเครื่องหมายคำพูดเดี่ยวซึ่งจะทำให้การฆ่าเชื้อง่ายขึ้น
Stéphane Chazelas
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.