Bagaimana cara menggunakan baris file sebagai argumen dari suatu perintah?

159

Katakanlah, saya punya file yang foo.txtmenentukan Nargumen

arg1
arg2
...
argN

yang perlu saya sampaikan ke perintah my_command

Bagaimana cara menggunakan baris file sebagai argumen dari suatu perintah?

Yoo
sumber
10
"argumen", jamak, atau "argumen", tunggal? Jawaban yang diterima benar hanya dalam kasus argumen tunggal - yang merupakan apa yang diminta oleh teks tubuh - tetapi tidak dalam kasus multi-argumen, di mana topik tersebut tampaknya menanyakan.
Charles Duffy
4 jawaban di bawah ini biasanya memiliki hasil yang identik, namun mereka memiliki semantik yang sedikit berbeda. Sekarang saya ingat mengapa saya berhenti menulis skrip bash: P
Navin

Jawaban:

237

Jika shell Anda adalah bash (antara lain), pintasannya $(cat afile)adalah $(< afile), jadi Anda akan menulis:

mycommand "$(< file.txt)"

Didokumentasikan di halaman bash man di bagian 'Pergantian Perintah'.

Alterately, minta perintah Anda dibaca dari stdin, jadi: mycommand < file.txt

glenn jackman
sumber
11
Menjadi bertele-tele, itu bukan jalan pintas untuk menggunakan <operator. Ini berarti bahwa shell itu sendiri akan melakukan redirection daripada mengeksekusi catbinary untuk properti redirection itu.
lstyls
2
Ini adalah pengaturan kernel sehingga Anda harus mengkompilasi ulang kernel. Atau selidiki perintah xargs
glenn jackman
3
@Ykaner karena tanpa "$(…)", isi file.txtakan diteruskan ke input standar mycommand, bukan sebagai argumen. "$(…)"berarti menjalankan perintah dan mengembalikan output sebagai string ; di sini "perintah" hanya membaca file tetapi bisa jadi lebih kompleks.
törzsmókus
3
Ini tidak berfungsi untuk saya kecuali saya kehilangan tanda kutip. Dengan tanda kutip, dibutuhkan seluruh file sebagai 1 argumen. Tanpa tanda kutip, itu menafsirkan setiap baris sebagai argumen terpisah.
Pete
1
@ LarsH Anda benar sekali. Itu cara yang kurang membingungkan untuk mengatakannya, terima kasih.
lstyls
37

Seperti yang telah disebutkan, Anda dapat menggunakan backticks atau $(cat filename).

Apa yang tidak disebutkan, dan saya pikir penting untuk dicatat, adalah bahwa Anda harus ingat bahwa shell akan memecah konten file tersebut sesuai dengan spasi, memberikan setiap "kata" yang ditemukannya pada perintah Anda sebagai argumen. Dan sementara Anda mungkin bisa melampirkan argumen baris perintah dalam tanda kutip sehingga bisa berisi spasi, urutan melarikan diri, dll., Membaca dari file tidak akan melakukan hal yang sama. Misalnya, jika file Anda mengandung:

a "b c" d

argumen yang akan Anda dapatkan adalah:

a
"b
c"
d

Jika Anda ingin menarik setiap baris sebagai argumen, gunakan konstruksi while / read / do:

while read i ; do command_name $i ; done < filename
Akan
sumber
Seharusnya saya sebutkan, saya berasumsi bahwa Anda menggunakan bash. Saya menyadari bahwa ada cangkang lain di luar sana, tetapi hampir semua mesin * nix yang saya kerjakan berjalan dengan bash atau yang setara. IIRC, sintaks ini harus bekerja sama pada ksh dan zsh.
Will
1
Baca harus read -rkecuali Anda ingin memperluas urutan backslash-escape - dan NUL adalah pembatas yang lebih aman untuk digunakan daripada baris baru, terutama jika argumen yang Anda sampaikan adalah hal-hal seperti nama file, yang dapat berisi baris baru literal. Juga, tanpa membersihkan IFS, Anda bisa memimpin dan mengikuti spasi kosong secara implisit dibersihkan i.
Charles Duffy
18
command `< file`

akan meneruskan konten file ke perintah pada stdin, tetapi akan menghapus baris baru, yang berarti Anda tidak dapat mengulangi setiap baris secara terpisah. Untuk itu Anda bisa menulis skrip dengan loop 'untuk':

for line in `cat input_file`; do some_command "$line"; done

Atau (varian multi-baris):

for line in `cat input_file`
do
    some_command "$line"
done

Atau (varian multi-baris dengan $()alih - alih ``):

for line in $(cat input_file)
do
    some_command "$line"
done

Referensi:

  1. Untuk sintaks loop: https://www.cyberciti.biz/faq/bash-for-loop/
Wesley Rice
sumber
Juga, memasukkan < filebackticks berarti tidak benar-benar melakukan pengalihan commandsama sekali.
Charles Duffy
3
(Apakah orang-orang yang meng-upgrade ini benar-benar mengujinya? command `< file` Tidak berfungsi baik di bash atau POSIX sh; zsh adalah masalah yang berbeda, tapi itu bukan masalah yang ditanyakan oleh pertanyaan ini).
Charles Duffy
@CharlesDuffy Bisakah Anda lebih spesifik tentang cara kerjanya?
mwfearnley
@CharlesDuffy Ini berfungsi di bash 3.2 dan zsh 5.3. Ini tidak akan berfungsi di sh, ash and dash, tetapi tidak juga $ (<file).
Arnie97
1
@ mwfearnley, dengan senang hati memberikan kekhususan itu. Tujuannya adalah untuk mengulangi garis, kan? Letakkan Hello Worlddi satu baris. Anda akan melihatnya dijalankan pertama kali some_command Hello, kemudian some_command World, tidak some_command "Hello World" atau some_command Hello World.
Charles Duffy
15

Anda melakukannya dengan menggunakan backticks:

echo World > file.txt
echo Hello `cat file.txt`
Tommy Lacroix
sumber
4
Ini tidak menciptakan sebuah argumen - karena tidak dikutip, subjek itu string membelah, jadi jika Anda dipancarkan echo "Hello * Starry * World" > file.txtpada langkah pertama, Anda akan mendapatkan setidaknya empat argumen terpisah dilewatkan ke perintah kedua - dan kemungkinan lebih lanjut, karena *s akan diperluas ke nama-nama file yang ada di direktori saat ini.
Charles Duffy
3
... dan karena itu berjalan ekspansi gumpal, tidak memancarkan persis apa yang ada di file. Dan karena melakukan operasi substitusi perintah, itu sangat tidak efisien - itu benar-benar fork()keluar dari subkulit dengan FIFO yang melekat pada stdout, kemudian memanggil /bin/catsebagai anak dari subkulit itu, kemudian membaca output melalui FIFO; dibandingkan dengan $(<file.txt), yang membaca konten file menjadi bash secara langsung tanpa subshell atau FIFO yang terlibat.
Charles Duffy
11

Jika Anda ingin melakukan ini dengan cara yang kuat yang bekerja untuk setiap argumen baris perintah yang mungkin (nilai dengan spasi, nilai dengan baris baru, nilai dengan karakter kutipan literal, nilai yang tidak dapat dicetak, nilai dengan karakter glob, dll), itu akan sedikit lebih menarik.


Untuk menulis ke file, diberikan array argumen:

printf '%s\0' "${arguments[@]}" >file

... ganti dengan "argument one","argument two" , dll yang sesuai.


Untuk membaca dari file itu dan menggunakan kontennya (dalam bash, ksh93, atau shell terbaru lainnya dengan array):

declare -a args=()
while IFS='' read -r -d '' item; do
  args+=( "$item" )
done <file
run_your_command "${args[@]}"

Untuk membaca dari file itu dan menggunakan isinya (dalam shell tanpa array; perlu diketahui bahwa ini akan menimpa daftar argumen baris perintah lokal Anda, dan dengan demikian paling baik dilakukan di dalam suatu fungsi, sehingga Anda menimpa argumen fungsi dan tidak daftar global):

set --
while IFS='' read -r -d '' item; do
  set -- "$@" "$item"
done <file
run_your_command "$@"

Perhatikan bahwa -d(memungkinkan pembatas end-of-line yang berbeda untuk digunakan) adalah ekstensi non-POSIX, dan shell tanpa array juga mungkin tidak mendukungnya. Jika demikian, Anda mungkin perlu menggunakan bahasa non-shell untuk mengubah konten yang dibatasi NUL menjadi evalbentuk -safe:

quoted_list() {
  ## Works with either Python 2.x or 3.x
  python -c '
import sys, pipes, shlex
quote = pipes.quote if hasattr(pipes, "quote") else shlex.quote
print(" ".join([quote(s) for s in sys.stdin.read().split("\0")][:-1]))
  '
}

eval "set -- $(quoted_list <file)"
run_your_command "$@"
Charles Duffy
sumber
6

Inilah cara saya meneruskan konten file sebagai argumen ke perintah:

./foo --bar "$(cat ./bar.txt)"
kenyal
sumber
1
Ini - tetapi karena secara substansial kurang efisien - secara efektif setara dengan $(<bar.txt)(saat menggunakan shell, seperti bash, dengan ekstensi yang sesuai). $(<foo)adalah kasus khusus: tidak seperti substitusi perintah biasa, seperti yang digunakan $(cat ...), itu tidak memotong subkulit untuk beroperasi, dan dengan demikian menghindari banyak overhead.
Charles Duffy
3

Jika semua yang perlu Anda lakukan adalah mengubah file arguments.txtdengan konten

arg1
arg2
argN

ke dalam my_command arg1 arg2 argNmaka Anda cukup menggunakan xargs:

xargs -a arguments.txt my_command

Anda dapat memasukkan argumen statis tambahan dalam xargspanggilan, seperti xargs -a arguments.txt my_command staticArgyang akan dipanggilmy_command staticArg arg1 arg2 argN

Cobra_Fast
sumber
1

Di bash shell saya yang berikut ini berfungsi seperti pesona:

cat input_file | xargs -I % sh -c 'command1 %; command2 %; command3 %;'

di mana input_file berada

arg1
arg2
arg3

Sebagai bukti, ini memungkinkan Anda untuk mengeksekusi banyak perintah dengan setiap baris dari input_file, trik kecil yang bagus yang saya pelajari di sini .

honkaboy
sumber
3
"Trik kecil yang menyenangkan" Anda berbahaya dari perspektif keamanan - jika Anda memiliki argumen yang berisi $(somecommand), Anda akan mendapatkan perintah yang dieksekusi daripada diteruskan sebagai teks. Demikian juga, >/etc/passwdakan diproses sebagai pengalihan dan ditimpa /etc/passwd(jika dijalankan dengan izin yang sesuai), dll.
Charles Duffy
2
Jauh lebih aman untuk melakukan hal berikut sebagai gantinya (pada sistem dengan ekstensi GNU): xargs -d $'\n' sh -c 'for arg; do command1 "$arg"; command2 "arg"; command3 "arg"; done' _- dan juga lebih efisien, karena ia meneruskan sebanyak mungkin argumen ke setiap shell daripada memulai satu shell per baris dalam file input Anda.
Charles Duffy
Dan tentu saja, hilangkan penggunaan yang tidak bergunacat
tripleee
0

Setelah mengedit jawaban @Wesley Rice beberapa kali, saya memutuskan bahwa perubahan saya menjadi terlalu besar untuk terus mengubah jawabannya daripada menulis sendiri. Jadi, saya memutuskan untuk menulis sendiri!

Baca setiap baris file di dan operasikan baris demi baris seperti ini:

#!/bin/bash
input="/path/to/txt/file"
while IFS= read -r line
do
  echo "$line"
done < "$input"

Ini datang langsung dari penulis Vivek Gite di sini: https://www.cyberciti.biz/faq/unix-howto-read-line-by-line-from-file/ . Dia mendapat pujian!

Sintaks: Baca baris demi baris file pada shell Bash Unix & Linux:
1. Sintaksnya adalah sebagai berikut untuk bash, ksh, zsh, dan semua shell lain untuk membaca file baris demi baris
2. while read -r line; do COMMAND; done < input.file
3. -rOpsi dilewatkan untuk membaca perintah mencegah backslash lolos dari ditafsirkan.
4. Tambahkan IFS=opsi sebelum membaca perintah untuk mencegah pemangkasan spasi awal / akhir -
5.while IFS= read -r line; do COMMAND_on $line; done < input.file


Dan sekarang untuk menjawab pertanyaan yang sekarang ditutup ini yang saya juga punya: Apakah mungkin untuk `git menambahkan` daftar file dari file?- inilah jawaban saya:

Perhatikan bahwa FILES_STAGEDini adalah variabel yang berisi path absolut ke file yang berisi banyak baris di mana setiap baris adalah path relatif ke file yang ingin saya lakukan git add. Cuplikan kode ini akan menjadi bagian dari file "eRCaGuy_dotfiles / useful_scripts / sync_git_repo_to_build_machine.sh" dalam proyek ini, untuk memudahkan sinkronisasi file dalam pengembangan dari satu PC (mis: komputer tempat saya berkode ) ke yang lain (mis: komputer tempat saya berkode )) komputer yang lebih kuat yang saya bangun): https://github.com/ElectricRCAircraftGuy/eRCaGuy_dotfiles .

while IFS= read -r line
do
    echo "  git add \"$line\""
    git add "$line" 
done < "$FILES_STAGED"

Referensi:

  1. Di mana saya menyalin jawaban saya dari: https://www.cyberciti.biz/faq/unix-howto-read-line-by-line-from-file/
  2. Untuk sintaks loop: https://www.cyberciti.biz/faq/bash-for-loop/

Terkait:

  1. Bagaimana cara membaca isi file baris demi baris dan lakukan git addpadanya: Apakah mungkin untuk `git menambah` daftar file dari file?
Gabriel Staples
sumber