bagaimana saya bisa mengutip ekspansi variabel di dalam string untuk menghindari pemisahan kata?

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

Dalam kasus seperti itu, bagaimana saya bisa mengutip $myvaruntuk menghindari pemisahan kata karena nilai spasi putih myvar?

Tim
sumber
4
Tidak masalah bagaimana Anda menyelesaikan ini, itu masih tidak akan banyak membantu. Satu- cdsatunya efek di dalam bash -cshell.
Kusalananda
Saya hanya memberikan contoh minimal untuk pertanyaan saja, itu bukan solusi yang berfungsi untuk masalah saya yang sebenarnya, yaitu unix.stackexchange.com/a/269080/674 ditambah bahwa dalam string perintah yang diberikan kepada sudo bash -csaya memiliki ekspansi variabel untuk pathname yang mungkin berisi spasi putih.
Tim

Jawaban:

13

Tidak ada pemisahan kata (seperti pada fitur yang membagi variabel pada ekspansi yang tidak dikutip) dalam kode tersebut karena $myvartidak tidak dikutip.

Namun ada kerentanan injeksi perintah seperti $myvaryang diperluas sebelum diteruskan ke bash. Jadi isinya ditafsirkan sebagai kode bash!

Spasi di sana akan menyebabkan beberapa argumen diteruskan cd, bukan karena pemisahan kata , tetapi karena mereka akan diuraikan sebagai beberapa token dalam sintaks shell. Dengan nilai bye;reboot, itu akan reboot! ¹

Di sini, Anda ingin:

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

(di mana Anda melewatkan konten $myvarsebagai argumen pertama dari skrip inline itu; perhatikan bagaimana keduanya $myvardan $1dikutip untuk shell masing-masing untuk mencegah IFS-word-splitting (and globbing)).

Atau:

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

(tempat Anda mengirimkan konten $myvardalam variabel lingkungan).

Tentu saja Anda tidak akan mencapai sesuatu yang bermanfaat dengan menjalankan hanya cd dalam skrip inline itu (selain memeriksa apakah rootbisa cdmasuk ke sana). Agaknya, Anda ingin skrip itu cdada di sana dan kemudian melakukan sesuatu yang lain di sana seperti:

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

Jika maksudnya adalah menggunakan sudountuk dapat cdmasuk ke direktori yang Anda tidak memiliki akses, maka itu tidak bisa benar-benar berfungsi.

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

akan memulai interaktif bashdengan direktori saat ini di $myvar. Tapi cangkang itu akan berjalan sebagai root.

Anda bisa melakukannya:

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

Untuk mendapatkan interaktif bashyang tidak terjangkau dengan direktori saat ini $myvar, tetapi jika Anda tidak memiliki izin untuk cdmasuk ke direktori itu di tempat pertama, Anda tidak akan dapat melakukan apa pun di direktori itu bahkan jika itu adalah direktori kerja Anda saat ini.

$ 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

Pengecualiannya adalah jika Anda memiliki izin pencarian ke direktori itu sendiri tetapi tidak ke salah satu komponen direktori dari jalurnya:

$ 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

¹ secara tegas, untuk nilai-nilai $myvarseperti $(seq 10)(secara harfiah), akan ada pemisahan kata tentu saja pada perluasan penggantian perintah oleh bash shell dimulai sebagairoot

Stéphane Chazelas
sumber
4

Ada sihir kutipan baru (bash 4.4) yang memungkinkan Anda untuk lebih langsung melakukan apa yang Anda inginkan. Tergantung pada konteks yang lebih besar, menggunakan salah satu teknik lain mungkin lebih baik, tetapi itu berfungsi dalam konteks terbatas ini.

sudo bash -c "cd ${myvar@Q}; pwd"
Seth Robertson
sumber
4

Jika Anda tidak dapat mempercayai konten $myvar, satu-satunya pendekatan yang masuk akal adalah menggunakan GNU printfuntuk membuat versi yang lolos dari itu:

#!/bin/bash

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

Seperti yang printfdikatakan halaman manual:

%q

ARGUMENT dicetak dalam format yang dapat digunakan kembali sebagai input shell, keluar dari karakter yang tidak dapat dicetak dengan $''sintaks POSIX yang diusulkan .

Toby Speight
sumber
Lihat jawaban saya untuk versi portabel ini.
R .. GitHub BERHENTI MEMBANTU ICE
GNU printfhanya akan dipanggil di sana pada sistem GNU dan jika itu $(printf '%q' "$myvar")dijalankan di shell di mana printftidak dibangun. Sebagian besar cangkang telah printfdibangun saat ini. Tidak semua mendukung %qdan ketika mereka melakukannya, mereka akan mengutip hal dalam format yang didukung oleh shell yang sesuai, yang mungkin tidak harus sama dengan yang dipahami oleh bash. Sebenarnya, bash's printfgagal untuk mengutip hal-hal dengan benar untuk bahkan dirinya sendiri untuk beberapa nilai-nilai $myvardi beberapa lokal.
Stéphane Chazelas
Dengan bash-4.3setidaknya, itu masih kerentanan injeksi perintah dengan myvar=$'\xa3``reboot`\xa3`' lokal zh_HK.big5hkscs misalnya.
Stéphane Chazelas
3

Pertama-tama, karena orang lain telah mencatat cdperintah Anda tidak berguna karena terjadi dalam konteks shell yang segera keluar. Tetapi secara umum pertanyaan itu masuk akal. Yang Anda butuhkan adalah cara untuk mengutip shell string yang sewenang-wenang , sehingga dapat digunakan dalam konteks di mana ia akan ditafsirkan sebagai string yang dikutip shell. Saya punya contoh di halaman trik sulap saya :

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

Dengan fungsi ini, Anda dapat melakukan:

myvar="/path to/my directory"
sudo bash -c "cd $(quote "$myvar")"
R .. GitHub BERHENTI MEMBANTU ICE
sumber
3

Apa yang Stéphane Chazelas sarankan adalah cara terbaik untuk melakukan ini. Saya hanya akan memberikan jawaban tentang cara mengutip dengan aman sintaks ekspansi parameter yang bertentangan dengan menggunakan sedsubproses, seperti dalam jawaban R ..

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

Output dari skrip itu adalah:

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

Ya, ada pemisahan kata.
Nah, secara teknis, baris perintah terpecah pada bash parsing baris.

Pertama mari kita mengutip dengan benar:

Solusi paling sederhana (dan tidak aman) untuk mendapatkan perintah untuk bekerja karena saya yakin Anda akan menginginkannya adalah:

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

Mari kita konfirmasi apa yang terjadi (dengan asumsi myvar="/path to/my directory"):

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

Seperti yang Anda lihat, string path terpecah pada spasi. Dan:

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

Tidak terpecah.

Namun, perintah (seperti yang tertulis) tidak aman. Penggunaan yang lebih baik:

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

Itu akan melindungi cd terhadap file yang dimulai dengan tanda hubung (-) atau tautan berikut yang dapat menyebabkan masalah.

Itu akan bekerja jika Anda dapat percaya bahwa string $myvaradalah jalur.
Namun, "injeksi kode" masih dimungkinkan karena Anda "mengeksekusi string":

$ 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

Anda membutuhkan kutipan string yang lebih kuat, seperti:

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

Seperti yang Anda lihat di atas, nilai tidak ditafsirkan tetapi hanya digunakan sebagai "$1'.

Sekarang kita dapat (dengan aman) menggunakan sudo:

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

Jika ada beberapa argumen, Anda dapat menggunakan:

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

Kutipan tunggal tidak akan berfungsi di sini, karena variabel Anda tidak akan diperluas. Jika Anda yakin bahwa variabel untuk jalur Anda dibersihkan, solusi paling sederhana adalah dengan hanya menambahkan tanda kutip di sekitar ekspansi yang dihasilkan, setelah itu dalam bash shell baru:

sudo bash -c "cd \"$myvar\""
cunninghamp3
sumber
2
Masih kerentanan perintah injeksi, seperti untuk nilai $myvarlike $(reboot)atau"; reboot; #
Stéphane Chazelas
1
menggunakan sudo bash -c "cd -P -- '$myvar'"akan membatasi masalah hanya nilai-nilai $myvar yang mengandung karakter kutipan tunggal yang akan lebih mudah disanitasi.
Stéphane Chazelas