Bagaimana cara mengeksekusi output dari perintah di dalam shell saat ini?

92

Saya sangat mengetahui utilitas source(alias .), yang akan mengambil konten dari file dan menjalankannya di dalam shell saat ini.

Sekarang, saya mengubah beberapa teks menjadi perintah shell, dan kemudian menjalankannya, sebagai berikut:

$ ls | sed ... | sh

lshanyalah contoh acak, teks aslinya bisa apa saja. sedjuga, hanya contoh untuk mengubah teks. Yang menarik adalah sh. Saya menyalurkan apa pun yang saya punya shdan menjalankannya.

Masalah saya adalah, itu berarti memulai sub shell baru. Saya lebih suka menjalankan perintah dalam shell saya saat ini. Seperti yang bisa saya lakukan dengan source some-file, jika saya memiliki perintah dalam file teks.

Saya tidak ingin membuat file temp karena terasa kotor.

Sebagai alternatif, saya ingin memulai sub shell saya dengan karakteristik yang sama persis dengan shell saya saat ini.

memperbarui

Oke, solusi menggunakan backtick pasti berhasil, tetapi saya sering perlu melakukan ini saat saya memeriksa dan mengubah output, jadi saya lebih suka jika ada cara untuk menyalurkan hasilnya menjadi sesuatu pada akhirnya.

pembaruan menyedihkan

Ah, /dev/stdinbenda itu terlihat sangat cantik, tetapi, dalam kasus yang lebih kompleks, tidak berhasil.

Jadi, saya punya ini:

find . -type f -iname '*.doc' | ack -v '\.doc$' | perl -pe 's/^((.*)\.doc)$/git mv -f $1 $2.doc/i' | source /dev/stdin

Yang memastikan semua .docfile memiliki ekstensi yang lebih rendah.

Dan yang kebetulan, bisa ditangani xargs, tapi bukan itu intinya.

find . -type f -iname '*.doc' | ack -v '\.doc$' | perl -pe 's/^((.*)\.doc)$/$1 $2.doc/i' | xargs -L1 git mv

Jadi, ketika saya menjalankan yang pertama, itu akan langsung keluar, tidak ada yang terjadi.

kch
sumber
Apakah perintah kompleks Anda berfungsi saat Anda menyalurkan ke file temp terlebih dahulu dan kemudian mengambilnya? Jika tidak, apa masalahnya dengan keluaran yang dihasilkan? Output dari perintah Anda tidak akan berfungsi jika nama file Anda memiliki spasi di dalamnya atau jika urutan tertentu tidak di-escape dengan benar. Saya ingin menambahkan kutipan minimal $ 1 dan $ 2.doc.
Kaleb Pederson
Apakah ada alasan bagus untuk menjalankan ini di shell aslinya? - contoh ini tidak memanipulasi shell saat ini sehingga Anda tidak mendapatkan apa-apa dengan melakukannya. Solusi cepatnya adalah Anda mengarahkan output ke file dan sumber file itu
nos
@ Kaleb output berjalan dengan baik. dalam kasus khusus ini, bahkan jika saya menyalurkan ke sh. nama file aman-ruang, tapi terima kasih atas perhatiannya. @nos git variabel lingkungan pada shell asli. dan sekali lagi, ini hanyalah contoh. pertanyaannya adalah untuk hidup.
kch
source / dev / stdin tidak berfungsi untuk saya ketika membutuhkan variabel yang ditetapkan untuk digunakan. geirha di freenode bash mengarahkan saya ke mywiki.wooledge.org/BashFAQ/024 dan menyarankan agar saya mencoba sumber substitusi proses <(perintah) yang berhasil untuk saya
srcerer

Jawaban:

88
$ ls | sed ... | source /dev/stdin

UPDATE: ini bekerja di bash 4.0, serta tcsh, dan dash (jika Anda mengubah sourceke .). Rupanya ini buggy di bash 3.2. Dari catatan rilis bash 4.0 :

Memperbaiki bug yang menyebabkan `. ' gagal membaca dan menjalankan perintah dari file non-reguler seperti perangkat atau pipa bernama.

mark4o
sumber
Oh, dan karena Anda mencantumkan cangkang, ini juga berfungsi di zsh.
kch
2
Saya pikir ini cerdik sampai saya mencobanya di msys / mingw (di mana tidak ada folder / dev (atau bahkan perangkat)! Saya mencoba banyak kombinasi eval, $ (), dan backticks ". Saya tidak dapat membuat apa pun berfungsi , jadi saya akhirnya hanya mengalihkan output ke file temp, mengambil file temp, dan menghapusnya. Pada dasarnya "sed ...> /tmp/$$.tmp &&. /tmp/$$.tmp && rm / tmp / $$. tmp ". Ada yang punya solusi msys / mingw tanpa file temp ???
chriv
10
Hanya sebuah komentar: Yang ini sepertinya tidak menangani pernyataan ekspor seperti yang diharapkan. Jika string beralur memiliki pernyataan ekspor, variabel exportet tidak tersedia di lingkungan terminal Anda setelahnya, sedangkan dengan eval export juga berfungsi dengan sempurna.
Phil
Dalam bash tanpa set opsi lastpipe, ini membuat subkulit seperti yang | bashakan
orang lain itu
1
Mengingat bahwa MacOS X biasanya memiliki bash 3.2 (mungkin Yosemite memiliki 4.0?), Dan kebutuhan tipuan lastpipe dalam kasus penggunaan saya (mengekspor semua variabel dalam file dengan pra-pemrosesan setiap baris dengan sed untuk menambahkan 'ekspor') saya memilih untuk mengikuti eval "$(sed ...)"pendekatan ini - tapi terima kasih atas perhatian 3.2 bug!
Alex Dupuy
135

The evalPerintah ada untuk tujuan ini.

eval "$( ls | sed... )"

Selengkapnya dari manual bash :

eval

          eval [arguments]

Argumen tersebut digabungkan menjadi satu perintah, yang kemudian dibaca dan dieksekusi, dan status keluarnya dikembalikan sebagai status keluar dari eval. Jika tidak ada argumen atau hanya argumen kosong, status kembaliannya adalah nol.

Juliano
sumber
8
Satu-satunya masalah di sini adalah Anda mungkin perlu menyisipkan; untuk memisahkan perintah Anda. Saya menggunakan metode ini sendiri untuk bekerja di AIX, Sun, HP, dan Linux.
Tanktalus
Tanktalus, terima kasih atas komentarnya, itu membuat skrip saya berfungsi. Pada mesin saya eval tidak memisahkan perintah pada baris baru dan menggunakan sumber tidak bekerja bahkan dengan titik koma. Evaluasi dengan titik koma adalah solusinya. Saya berharap saya bisa memberi Anda beberapa poin.
Milan Babuškov
2
@ MilanBabuškov: Saya memberi peringkat dia untuk Anda ;-)
Phil
3
Ini luar biasa. Namun, untuk kasus penggunaan saya, saya akhirnya harus memberi tanda kutip di sekitar $ () saya, seperti ini: eval "$( ssh remote-host 'cat ~/.bash_profile' )"Perhatikan bahwa saya memang menggunakan bash 3.2.
Zachary Murray
3
Ini bekerja untuk saya di mana jawaban yang diterima tidak.
Dan Tenenbaum
37

Wow, saya tahu ini adalah pertanyaan lama, tetapi saya menemukan diri saya dengan masalah yang persis sama baru-baru ini (begitulah cara saya sampai di sini).

Bagaimanapun - saya tidak suka source /dev/stdinjawabannya, tapi saya pikir saya menemukan yang lebih baik. Sebenarnya ini tampak sederhana:

echo ls -la | xargs xargs

Bagus kan? Sebenarnya, ini masih tidak melakukan apa yang Anda inginkan, karena jika Anda memiliki banyak baris, itu akan menggabungkannya menjadi satu perintah alih-alih menjalankan setiap perintah secara terpisah. Jadi solusi yang saya temukan adalah:

ls | ... | xargs -L 1 xargs

yang -L 1pilihan berarti Anda gunakan (paling) 1 baris per perintah eksekusi. Catatan: jika baris Anda diakhiri dengan spasi, itu akan digabungkan dengan baris berikutnya! Jadi pastikan setiap baris diakhiri dengan non-spasi.

Akhirnya, Anda bisa melakukannya

ls | ... | xargs -L 1 xargs -t

untuk melihat perintah apa yang dijalankan (-t adalah verbose).

Semoga seseorang membaca ini!

rabensky
sumber
31

Coba gunakan substitusi proses , yang menggantikan keluaran dari perintah dengan file sementara yang kemudian dapat bersumber:

source <(echo id)
rishta
sumber
7
Jenis sihir apakah ini?
paulotorrens
1
Ini juga pikiran pertama saya. Meskipun saya lebih suka solusi eval. @PauloTorrens <(xyz) cukup mengeksekusi xyz dan mengganti <(xyz) dengan nama file yang akan menulis output xyz. Sangat mudah untuk memahami cara kerjanya dengan melakukan misalnya echo <(echo id)/dev/fd/12cat <(echo id)idsource <(echo id)id
:,
2
@PauloTorrens, ini disebut substitusi proses . Lihat dokumen terkait untuk penjelasan resmi, tetapi jawaban singkatnya adalah "<()" adalah sintaks khusus yang dirancang untuk kasus seperti ini.
Mark Stosberg
1
@ MarkStosberg, apakah Anda tahu apakah ini sintaks khusus, atau hanya subkulit yang (...)dialihkan?
paulotorrens
2
@PauloTorrens, Ini adalah sintaks khusus hanya untuk substitusi proses.
Mark Stosberg
6

Saya yakin ini adalah "jawaban yang benar" untuk pertanyaan:

ls | sed ... | while read line; do $line; done

Artinya, seseorang dapat menyalurkan ke dalam whileloop; ituread perintah perintah mengambil satu baris dari yang stdindan penerima haknya untuk variabel $line. $linekemudian menjadi perintah yang dieksekusi dalam loop; dan itu berlanjut sampai tidak ada baris lagi dalam masukannya.

Ini masih tidak akan berfungsi dengan beberapa struktur kontrol (seperti loop lain), tetapi sesuai dengan tagihan dalam kasus ini.

Geoff Nixon
sumber
5
`ls | sed ...`

Saya merasa seperti ls | sed ... | source -akan lebih cantik, tapi sayangnya sourcetidak mengerti -maksudnya stdin.

kekacauan
sumber
setelah melihat jawaban mark4o, bukankah selama ini terasa seperti yang ada di wajah kita?
kch
Heh, ya. Saya tidak pernah ingat bahwa benda itu ada.
kekacauan
1

Saya rasa solusi Anda adalah penggantian perintah dengan backticks: http://tldp.org/LDP/Bash-Beginners-Guide/html/sect_03_04.html

Lihat bagian 3.4.5

Eric Wendelin
sumber
3
Jika Anda menggunakan bash, $( )sintaksnya mungkin lebih disukai. 'Course backticks bekerja di lebih banyak cangkang ...
dmckee --- mantan moderator kucing
6
$ (command) dan $ ((1 + 1)) bekerja di semua shell posix. Saya pikir banyak yang menunda dengan menandainya sebagai kesalahan sintaks, tapi itu hanya karena vim menyoroti untuk shell Bourne asli yang sangat sedikit digunakan. Untuk mendapatkan vim untuk menyoroti benar menempatkan ini dalam vimrc Anda: biarkan g: is_posix = 1
pixelbeat
1

Untuk menggunakan solusi mark4o pada bash 3.2 (macos), string di sini dapat digunakan sebagai pengganti pipeline seperti dalam contoh ini:

. /dev/stdin <<< "$(grep '^alias' ~/.profile)"
ish-west
sumber
atau gunakan backticks:. / dev / stdin <<< `grep '^ alias' ~ / .profile`
William H. Hooper
0

Mengapa tidak digunakan source?

$ ls | sed ... > out.sh ; source out.sh
Kaleb Pederson
sumber
Dia menyebutkan bahwa file temporer menjijikkan.
kekacauan