Bagaimana Saya Dapat Memperluas Tilde ~ Sebagai Bagian Dari Variabel?


12

Ketika saya membuka bash prompt dan ketik:

$ set -o xtrace
$ x='~/someDirectory'
+ x='~/someDirectory'
$ echo $x
+ echo '~/someDirectory'
~/someDirectory

Saya berharap bahwa baris ke-5 di atas akan pergi + echo /home/myUsername/someDirectory. Apakah ada cara untuk melakukan ini? Dalam skrip Bash asli saya, variabel x sebenarnya sedang diisi dari data dari file input, melalui loop seperti ini:

while IFS= read line
do
    params=($line)
    echo ${params[0]}
done <"./someInputFile.txt"

Meski begitu, saya mendapatkan hasil yang serupa, dengan echo '~/someDirectory'bukannya echo /home/myUsername/someDirectory.


Di ZSH ini x='~'; print -l ${x} ${~x}. Saya menyerah setelah menggali bashmanual untuk sementara waktu.
hancurkan

@ thrig: Ini bukan bashism, perilaku ini POSIX.
WhiteWinterWolf

Sangat terkait erat (jika bukan penipuan): unix.stackexchange.com/questions/151850/…
Kusalananda

@ Kusalananda: Saya tidak yakin ini adalah penipuan karena alasannya di sini agak berbeda: OP tidak menyertakan $ x antara tanda kutip ketika menggemakannya.
WhiteWinterWolf

Anda tidak memasukkan tilde ke file input. Masalah terpecahkan.
chepner

Jawaban:


11

Standar POSIX memaksakan ekspansi kata untuk dilakukan dalam urutan berikut (menekankan milikku):

  1. Ekspansi Tilde (lihat Ekspansi Tilde), ekspansi parameter (lihat Ekspansi Parameter), substitusi perintah (lihat Substitusi Perintah), dan ekspansi aritmatika (lihat Ekspansi Aritmatika) harus dilakukan, mulai dari awal hingga akhir. Lihat item 5 dalam Pengakuan Token.

  2. Pemisahan bidang (lihat Pemisahan Bidang) harus dilakukan pada bagian-bagian dari bidang yang dihasilkan oleh langkah 1, kecuali IFS adalah nol.

  3. Perluasan Pathname (lihat Perluasan Pathname) harus dilakukan, kecuali set -f berlaku.

  4. Penghapusan kutipan (lihat Penghapusan Penawaran) akan selalu dilakukan terakhir.

Satu-satunya poin yang menarik bagi kami di sini adalah yang pertama: seperti yang Anda lihat ekspansi tilde diproses sebelum ekspansi parameter:

  1. Shell mencoba ekspansi tilde aktif echo $x, tidak ada tilde yang ditemukan, jadi ia melanjutkan.
  2. Shell mencoba ekspansi parameter pada echo $x, $xditemukan dan diperluas dan baris perintah menjadi echo ~/someDirectory.
  3. Pemrosesan berlanjut, ekspansi tilde telah diproses ~karakter tetap apa adanya.

Dengan menggunakan tanda kutip saat menetapkan $x, Anda secara eksplisit meminta untuk tidak memperluas tilde dan memperlakukannya seperti karakter normal. Suatu hal yang sering terlewatkan adalah bahwa dalam perintah shell Anda tidak perlu mengutip seluruh string, sehingga Anda dapat membuat ekspansi terjadi saat penugasan variabel:

user@host:~$ set -o xtrace
user@host:~$ x=~/'someDirectory'
+ x=/home/user/someDirectory
user@host:~$ echo $x
+ echo /home/user/someDirectory
/home/user/someDirectory
user@host:~$

Dan Anda juga dapat membuat ekspansi terjadi pada echocommand-line selama itu bisa terjadi sebelum ekspansi parameter:

user@host:~$ x='someDirectory'
+ x=someDirectory
user@host:~$ echo ~/$x
+ echo /home/user/someDirectory
/home/user/someDirectory
user@host:~$

Jika karena alasan tertentu Anda benar-benar perlu memengaruhi tilde ke $xvariabel tanpa ekspansi, dan dapat memperluasnya pada echoperintah, Anda harus melanjutkan dua kali untuk memaksa dua ekspansi $xvariabel terjadi:

user@host:~$ x='~/someDirectory'
+ x='~/someDirectory'
user@host:~$ echo "$( eval echo $x )"
++ eval echo '~/someDirectory'
+++ echo /home/user/someDirectory
+ echo /home/user/someDirectory
/home/user/someDirectory
user@host:~$ 

Namun, perlu diketahui bahwa tergantung pada konteks di mana Anda menggunakan struktur seperti itu mungkin memiliki efek samping yang tidak diinginkan. Sebagai aturan praktis, lebih memilih untuk menghindari menggunakan apa pun yang diperlukan evalketika Anda memiliki cara lain.

Jika Anda ingin secara khusus mengatasi masalah tilde yang bertentangan dengan segala jenis ekspansi lainnya, struktur seperti itu akan lebih aman dan portabel:

user@host:~$ x='~/someDirectory'
+ x='~/someDirectory'
user@host:~$ case "$x" in "~/"*)
>     x="${HOME}/${x#"~/"}"
> esac
+ case "$x" in
+ x=/home/user/someDirectory
user@host:~$ echo $x
+ echo /home/user/someDirectory
/home/user/someDirectory
user@host:~$ 

Struktur ini secara eksplisit memeriksa keberadaan arahan ~dan menggantinya dengan dir home user jika ditemukan.

Mengikuti komentar Anda, x="${HOME}/${x#"~/"}"mungkin memang mengejutkan bagi seseorang yang tidak digunakan dalam pemrograman shell, tetapi sebenarnya terkait dengan aturan POSIX yang sama seperti yang saya kutip di atas.

Seperti yang diberlakukan oleh standar POSIX, penghapusan penawaran terjadi terakhir dan ekspansi parameter terjadi sangat awal. Dengan demikian, ${#"~"}dievaluasi dan diperluas jauh sebelum evaluasi kutipan luar. Secara bergantian, sebagaimana didefinisikan dalam aturan ekspansi Parameter :

Dalam setiap kasus bahwa nilai kata diperlukan (berdasarkan status parameter, seperti yang dijelaskan di bawah), kata harus mengalami ekspansi tilde, ekspansi parameter, substitusi perintah, dan ekspansi aritmatika.

Dengan demikian, sisi kanan #operator harus dikutip atau melarikan diri dengan benar untuk menghindari ekspansi tilde.

Jadi, untuk menyatakannya secara berbeda, ketika penafsir shell melihat x="${HOME}/${x#"~/"}", ia melihat:

  1. ${HOME}dan ${x#"~/"}harus diperluas.
  2. ${HOME}diperluas ke konten $HOMEvariabel.
  3. ${x#"~/"}memicu ekspansi bersarang: "~/"diuraikan tetapi, dikutip, diperlakukan sebagai literal 1 . Anda bisa menggunakan tanda kutip tunggal di sini dengan hasil yang sama.
  4. ${x#"~/"}ekspresi itu sendiri sekarang diperluas, menghasilkan awalan ~/yang dihapus dari nilai $x.
  5. Hasil di atas sekarang disatukan: ekspansi ${HOME}, literal /, ekspansi ${x#"~/"}.
  6. Hasil akhirnya terlampir dalam tanda kutip ganda, yang secara fungsional mencegah pemisahan kata. Saya katakan fungsional di sini karena ini tanda kutip ganda secara teknis tidak diperlukan (lihat di sini dan di sana misalnya), tetapi sebagai gaya pribadi segera sebagai tugas mendapat apa pun di luar a=$bsaya biasanya merasa lebih jelas add tanda kutip ganda.

By-the-way, jika melihat lebih dekat dengan casesintaks, Anda akan melihat "~/"*konstruksi yang bergantung pada konsep yang sama seperti yang x=~/'someDirectory'saya jelaskan di atas (di sini sekali lagi, tanda kutip ganda dan sederhana dapat digunakan secara bergantian).

Jangan khawatir jika hal-hal ini mungkin tampak tidak jelas pada pandangan pertama (mungkin bahkan pada pandangan kedua atau selanjutnya!). Menurut pendapat saya, ekspansi parameter adalah, dengan subkulit, salah satu konsep yang paling kompleks untuk dipahami saat pemrograman dalam bahasa shell.

Saya tahu bahwa beberapa orang mungkin sangat tidak setuju, tetapi jika Anda ingin belajar pemrograman shell lebih mendalam, saya mendorong Anda untuk membaca Panduan Skrip Bash Lanjutan : mengajarkan skrip Bash, jadi dengan banyak ekstensi dan lonceng-dan- peluit dibandingkan dengan scripting shell POSIX, tapi saya menemukan itu ditulis dengan baik dengan banyak contoh praktis. Setelah Anda mengelola ini, mudah untuk membatasi diri Anda ke fitur POSIX ketika Anda perlu, saya pribadi berpikir bahwa memasukkan langsung di bidang POSIX adalah kurva belajar curam yang tidak perlu untuk pemula (bandingkan penggantian tilde POSIX saya dengan Bash seperti regex @ m0dular's seperti Bash setara untuk mendapatkan ide tentang apa yang saya maksud;)!).


1 : Yang menuntun saya menemukan bug di Dash yang tidak menerapkan ekspansi tilde di sini dengan benar (penggunaan yang dapat diverifikasi x='~/foo'; echo "${x#~/}"). Ekspansi parameter adalah bidang yang kompleks untuk pengguna dan pengembang shell itu sendiri!


Bagaimana bash shell menguraikan garis x="${HOME}/${x#"~/"}"? Ini terlihat seperti gabungan dari 3 string: "${HOME}/${x#", ~/, dan "}". Apakah shell memungkinkan untuk tanda kutip ganda bersarang ketika pasangan dalam tanda kutip ganda berada di dalam ${ }blok?
Andrew

@Andrew: Saya telah melengkapi jawaban saya dengan informasi tambahan semoga menanggapi komentar Anda.
WhiteWinterWolf

Terima kasih, ini jawaban yang bagus. Saya telah belajar banyak dari membacanya. Seandainya saya bisa mengunggah lebih dari satu kali :)
Andrew

@WhiteWinterWolf: masih, shell tidak melihat tanda kutip bersarang apa pun hasilnya.
avp

6

Satu jawaban yang mungkin:

eval echo "$x"

Karena Anda membaca input dari file, saya tidak akan melakukan ini.

Anda dapat mencari dan mengganti ~ dengan nilai $ HOME, seperti ini:

x='~/.config'
x="${x//\~/$HOME}"
echo "$x"

Memberi saya:

/home/adrian/.config

Perhatikan bahwa ${parameter/pattern/string}ekspansi adalah ekstensi Bash dan mungkin tidak tersedia di shell lain.
WhiteWinterWolf

Benar. OP memang menyebutkan dia menggunakan Bash, jadi saya pikir itu jawaban yang tepat.
m0dular

Saya setuju, selama orang tetap menggunakan Bash mengapa tidak memanfaatkan sepenuhnya (tidak semua orang membutuhkan portabilitas di mana-mana), tapi cukup layak untuk dicatat untuk pengguna non-Bash (beberapa distro sekarang dikirimkan dengan Dash bukan Bash misalnya ) sehingga pengguna yang terpengaruh tidak terkejut.
WhiteWinterWolf

Saya mengambil kebebasan untuk menyebutkan pos Anda dalam penyimpangan saya tentang perbedaan antara ekstensi Bash dan skrip POSIX shell, karena saya pikir pernyataan Bex seperti garis-tunggal Bash Anda dibandingkan dengan casestruktur POSIX saya menggambarkan dengan baik bagaimana skrip Bash lebih ramah pengguna khususnya untuk pemula.
WhiteWinterWolf
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.