Mungkin dalam bash? Mengubah ekspansi perintah menjadi argumen

0

Apakah satu-satunya opsi untuk perluasan perintah bash:

  • tanda kutip $(..)yang outputnya selalu diuraikan dengan pemisahan string dumb, atau
  • dikutip "$(..)"output siapa yang selalu diteruskan sebagai satu unit?

Saya mencoba meniru di bash fungsi shell ikan yang saya buat untuk digunakan di Mac OS. Fungsi ikan saya selection https://superuser.com/a/1165855 mengambil pilihan di jendela paling depan dan menampilkan jalur untuk digunakan dalam substitusi perintah seperti ls -l (selection). Saya berharap dapat mencapai hal yang sama di bash mungkin sebagai ls -l $(selection).

Saya pikir itu adalah masalah mengutip dan jadi mencoba melewati jalur linefeed-delimited ke bash's printf "%q ". Namun saya menemukan bahwa tidak peduli apa pun kutipan yang saya masukkan output substitusi perintah, itu terbagi di spasi putih.

Contoh sederhana:

$ touch file\ a file\ b
$ ls $( echo "file\ a file\ b" ) # want expansion equiv to: ls 'file a' 'file b'
ls: a: No such file or directory
ls: b: No such file or directory
ls: file\: No such file or directory
ls: file\: No such file or directory

Itu tidak akan menjadi akhir dari dunia jika saya harus menggunakan substitusi perintah yang dikutip seperti ls -l "$(selection)"tetapi melakukan bahwa output perintah tidak pernah terpecah, tidak pernah memperhatikan pengamatan saya yang cermat. Apakah sintaks backtick yang lama berbeda?

Lucu, bash, Anda punya banyak fitur. Apakah tidak ada yang mengizinkan cmda $(cmdb-that-generates-parameters-for-cmda)? Atau apakah pengguna bash menghindari spasi atau simbol dalam nama file (seperti binatang) untuk membuat semuanya mudah? Terima kasih atas jawaban apa pun.

Pierre Houston
sumber

Jawaban:

2

Ada banyak cara untuk melakukan apa yang Anda inginkan. Secara default bashmenggunakan spasi putih sebagai pemisah default, tetapi Anda dapat dengan mudah menimpanya menggunakan IFS(Pemisah Bidang Internal) atau menggunakan teknik yang berbeda seperti enumerasi. Di bawah ini adalah beberapa contoh yang pertama kali terlintas dalam pikiran.

Varian # 1

Menggunakan variabel internal khusus IFS(Pemisah Bidang Internal)

#!/bin/bash

touch 'file a' 'file b'

# Set IFS to new line as a separator
IFS='
'
ls -la $( echo 'file a'; echo 'file b' )

Varian # 2

Menggunakan forloop

#!/bin/bash

touch 'file a' 'file b'
for variable in 'file a' 'file b';do
  ls -la "${variable}"
done

Varian # 3

Menggunakan forloop dari nilai yang telah ditentukan

#!/bin/bash

MultiArgs='
arg 0 1
arg 0  2
arg 0   3
arg 0    N
'

# Using only new line as a separator
IFS='
'

for variable in ${MultiArgs};do
  touch "${variable}"
  ls -la "${variable}"
done

Varian # 4

Menggunakan ~(tilda) sebagai pemisah

#!/bin/bash

MultiArgs='arg 0 1~arg 0 2~arg0 3~ar g 0 N' # Arguments with spaces

IFS='~'
for file in ${MultiArgs};do
 touch "${file}"
 ls -la "${file}";
done

Apakah sintaks backtick yang lama berbeda?

Tidak, itu sama tetapi backtick memiliki beberapa batasan yang $()tidak.

Atau apakah pengguna bash menghindari spasi atau simbol dalam nama file (seperti binatang) untuk membuat semuanya mudah?

Tidak, itu OK untuk menggunakan spasi dalam nama file sejauh orang akan menggunakan kutipan yang benar.

Sekitar $(cmdb-that-generates-parameters-for-cmda)

#!/bin/bash

# first command generate text for `sed` command that replacing . to ###
echo $( for i in {1..5}; do echo "${i} space .";done | sed 's/\./###/g' )

#############################################
# Another example: $() inside of another $()
for i in {1..5}; do touch "${i} x.file";done # Prepare some files with spaces in filenames

IFS='
'
echo "$( ls -la $(for i in ls *.file; do echo "$i"; done))"

Jika Anda ingin melewatkan semua parameter dalam satu baris untuk memberi makan program transcodeAnda, Anda dapat menambahkan hingga akhir ~/.bashrc fungsi file berikut:

_tr() {
  local debug
  debug=1
  [ $debug -eq 1 ] && {
    echo "Number of Arguments: $#"
    echo "Arguments: $@"
  }

  transcode "$@"
}

dan panggil fungsi ini dari baris perintah seperti itu:

eval _tr $(echo "$out")

Dimana variabel outharus seperti itu: out="'file a' 'file b'".
Jika akan mengetikkan nama file secara manual maka panggilan ke _trfungsi mungkin terlihat seperti:

eval _tr $(echo "'file a' 'file b'")

Jika Anda akan menggunakan beberapa skrip eksternal sebagai pengganti $()skrip eksternal / program harus mengembalikan daftar file yang dikutip persis seperti ini:

"'file a' 'file b'"
Alex
sumber
ubah dari "$ {i} .file" ke "$ {i} x.file" ... apa yang Anda katakan tentang boleh-boleh saja menggunakan spasi? ; ^)
Pierre Houston
Dan saya mencari untuk melakukan sesuatu seperti ini sebagai kenyamanan dari baris perintah, bukan dari skrip, jadi boilerplate seperti pengaturan IFS dll tidak tepat. Tetapi terima kasih, terutama untuk jawaban langsung atas pertanyaan spesifik saya
Pierre Houston
@PierreHouston Bug diperbaiki :)))
Alex
@PierreHouston Untuk operasi baris perintah (tanpa membuat skrip) gunakan varian # 2, cukup pisahkan perintah dengan ;karakter
Alex
Perbaikan bug cepat yang bagus: thumbsup :. Saya tidak melihat bagaimana # 2 membantu saya sama sekali. Aku (hampir) secara harfiah ingin mengetik pada prompt bash: cmda $(cmdb-that-generates-parameters-for-cmda), contoh yang lebih harfiah: transcode $(selection). Sepertinya tidak mungkin, saya akan mulai menggunakan shell ikan di Mac pekerjaan saya.
Pierre Houston
1

Dengan xargs:

echo '"file a" "file b"' | xargs ls

Pikirkan kutipannya. Note xargsbukan Bash bawaan (saya sebutkan ini dalam konteks "Bash, Anda punya banyak fitur").

Dengan evalyang merupakan Bash builtin:

eval ls $( echo '"file a" "file b"' )

Sekali lagi, perhatikan kutipannya. Ini bisa menjadi bumerang dengan mudah . Metode yang didasarkan pada perubahan IFS( jawaban lain ini ) tampaknya lebih aman. Di sisi lain evalAnda bahkan dapat melakukan ini:

eval ls $( echo '"file "{a,b}' )

Sayangnya tidak ada yang sesederhana ls (selection)sintaksis shell lain yang Anda mulai. Namun Anda dapat menyederhanakan sintaksis dari setiap solusi (?) Dengan fungsi kustom, misalnya:

_(){ "$2" | xargs "$1"; }

Ini memungkinkan Anda untuk menelepon _ ls selection, jika hanya selectionmenghasilkan input yang xargsbenar. Lebih baik lagi, ini:

_(){ "${@:$#}" | xargs "${@:1:$(($#-1))}"; }

membuat _ ls -l "file c" selectionmungkin. Saya kira Anda dapat membuat fungsi serupa yang menggunakan metode "ubah IFS" dari jawaban lain.

Namun, ini tidak sefleksibel dan seanggun ikan ls (selection). Saya berharap Anda bisa melakukannya ls (selection1) (selection2)pada ikan; _fungsi di atas tidak mencakup kasus ini.

Di sisi lain eval ls $(selection1) $(selection2)dapat bekerja jika selection1dan selection2mengembalikan string yang disanitasi dengan kutipan dan / atau melarikan diri yang tepat. Jika penyerang dapat membuat Anda memilih file bernama ; rm -rf ~/;Anda sebaiknya membersihkan dengan baik; atau tidak digunakan evaldi tempat pertama.

Kamil Maciorowski
sumber
Benar, saya tidak berbicara tentang bash saja, tetapi kegunaan baris perintah ketika menggunakan bash shell di aplikasi terminal pada sistem unix-y, khususnya Mac OS dalam kasus saya. Jadi semua tanda menunjuk ke Tidak, bash tidak bisa melakukan ini. Terima kasih.
Pierre Houston
dimaksudkan untuk mengatakan .. bash tidak dapat melakukan ini tanpa menggunakan eval.
Pierre Houston