Meminta vi melalui find | xargs merusak terminal saya. Mengapa?

137

Saat menjalankan vimmelalui find | xargs, seperti ini:

find . -name "*.txt" | xargs vim

Anda mendapat peringatan tentang

Input is not from a terminal

dan terminal dengan perilaku yang cukup rusak sesudahnya. Mengapa demikian?

DevSolar
sumber
11
Catatan: Anda dapat melakukan operasi ini sepenuhnya dalam vim, tidak menggunakan findatau tidak xargssama sekali. Buka vim tanpa argumen, lalu jalankan :args **/*.txt<CR>untuk mengatur argumen vim dari dalam editor.
Trevor Powell
3
@TrevorPowell: Selama ini, vim tidak pernah berhenti membuat saya takjub.
DevSolar

Jawaban:

100

Ketika Anda menjalankan suatu program melalui xargs, stdin (input standar) program menunjuk ke /dev/null. (Karena xargs tidak mengetahui stdin asli , ia melakukan hal terbaik berikutnya.)

$ true | xargs filan -s
    0 chrdev / dev / null
    1 tty / dev / pts / 1
    2 tty / dev / pts / 1

$ true | xargs ls -l / dev / fd /

Vim mengharapkan stdinnya sama dengan terminal pengendali, dan melakukan berbagai ioctl terkait terminal pada stdin secara langsung. Ketika dilakukan pada /dev/null(atau deskriptor file non-tty), ioctl tersebut tidak ada artinya dan mengembalikan ENOTTY, yang diabaikan secara diam-diam.

  • Dugaan saya pada penyebab yang lebih spesifik: Saat startup, Vim membaca dan mengingat pengaturan terminal lama, dan mengembalikannya saat keluar. Dalam situasi kami, ketika "pengaturan lama" diminta untuk non-tty fd (deskriptor file), Vim menerima semua nilai kosong dan semua opsi dinonaktifkan, dan dengan ceroboh menetapkan hal yang sama ke terminal Anda.

    Anda dapat melihat ini dengan menjalankan vim < /dev/null, keluar, lalu berjalan stty, yang akan menghasilkan banyak <undef>s. Di Linux, menjalankan stty saneakan membuat terminal dapat digunakan kembali (meskipun akan kehilangan opsi seperti itu iutf8, kemungkinan menyebabkan gangguan kecil di kemudian hari).

Anda dapat menganggap ini sebagai bug di Vim, karena dapat membuka /dev/ttyuntuk kontrol terminal, tetapi tidak. (Pada titik tertentu saat startup, Vim menduplikasi stderr ke stdin, yang memungkinkannya untuk membaca perintah input Anda - dari fd yang dibuka untuk menulis - tetapi bahkan itu tidak dilakukan cukup awal.)

grawity
sumber
20
+1, dan untuk TL; orang DR baru saja menjalankanstty sane
doc_id
@rahmanisback: Jawaban lainnya, ditambah komentar Trevor, semuanya menyediakan cara untuk menghindari kerusakan terminal. Saya menerima jawaban grawity, karena pertanyaan saya adalah "mengapa", bukan "bagaimana cara menghindarinya" - yang dicakup oleh pertanyaan lain yang sebenarnya melahirkan pertanyaan ini.
DevSolar
@DevSolar Paham, tetapi pikirkan tentang orang-orang frustrasi seperti saya yang baru saja google cara menyingkirkan perilaku itu sementara tidak -sayangnya - punya cukup waktu sekarang untuk mempelajari "mengapa", yang sangat menarik.
doc_id
4
ketika terminal saya rusak, seperti ini, saya gunakan resetbukan stty sanedan itu berfungsi dengan baik setelah itu.
Capi Etheriel
137

(Menyusul dari penjelasan grawity, itu xargsmenunjuk stdinke /dev/null.)

The solusi untuk masalah ini adalah dengan menambahkan -oparameter ke xargs. Dari man xargs:

-o

      Buka kembali stdin seperti /dev/ttypada proses anak sebelum menjalankan perintah. Ini berguna jika Anda ingin xargsmenjalankan aplikasi interaktif.

Dengan demikian, baris kode berikut dapat digunakan untuk Anda:

Temukan . -nama "* .txt" | xargs -o vim

GNU xargs mendukung ekstensi ini sejak beberapa rilis pada 2017 (dengan nama opsi panjang --open-tty).

Untuk versi xarg yang lebih lama atau yang lain, Anda dapat secara eksplisit masuk /dev/ttyuntuk menyelesaikan masalah:

find . -name "*.txt" | xargs bash -c '</dev/tty vim "$@"' ignoreme

(Yang ignoremeada untuk mengambil $ 0, sehingga $ @ adalah semua argumen dari xargs.)

James McGuigan
sumber
2
Bagaimana Anda membuat alias bash dari ini? $@tampaknya tidak menerjemahkan argumen dengan benar.
zanegray
1
@zanegray - Anda tidak bisa membuat alias, tetapi Anda bisa menjadikannya fungsi. Coba:function vimin () { xargs sh -c 'vim "$@" < /dev/tty' vim; }
Christopher
Untuk penjelasan terperinci tentang bagaimana solusi xargs GNU bekerja, dan mengapa Anda membutuhkan ignoremestring dummy , lihat vi.stackexchange.com/a/17813
wisbucky
@zanegray, Anda bisa menjadikannya sebagai alias. Kutipannya rumit. Lihat solusi di vi.stackexchange.com/a/17813
wisbucky
The -J, -o, -P and -R options are non-standard FreeBSD extensions which may not be available on other operating systems.(Itu tidak tersedia di macOS untuk saya karena saya menginstal xargs dari homebrew (yang GNU))
localhostdotdev
33

Cara termudah:

vim $(find . -name "*foo*")
trolol
sumber
5
Pertanyaan utamanya adalah "mengapa", bukan "bagaimana cara menghindarinya", dan sudah dijawab dengan kepuasan dua setengah tahun yang lalu.
DevSolar
5
Ini, tentu saja, tidak berfungsi dengan baik ketika nama file mengandung spasi atau karakter khusus lainnya, dan juga risiko keamanan.
Dejay Clayton
1
Jawaban favorit saya karena ini berfungsi untuk setiap perintah yang mencantumkan file, bukan hanya "menemukan" atau wildcard. Itu memang membutuhkan sedikit kepercayaan, seperti ditunjukkan Dejay.
Travis Wilson
1
Ini tidak akan berfungsi dengan banyak kasus penggunaan, xargs dirancang untuk: misalnya, ketika jumlah jalur sangat tinggi (cc @TravisWilson)
Orang Baik
21

Seharusnya berfungsi dengan baik jika Anda menggunakan opsi -exec pada find daripada memipis ke xargs.

find . -type f -name filename.txt -exec vi {} + 
Chris Wraith
sumber
2
Huh ... triknya ada +(alih-alih "yang biasa" \;) untuk memasukkan semua file yang ditemukan ke dalam satu sesi Vim - opsi yang saya selalu lupa. Anda benar, tentu saja, dan +1 untuk itu. Saya menggunakan vim $(find ...)hanya karena kebiasaan. Namun, saya sebenarnya bertanya mengapa operasi pipa mengacaukan terminal, dan grawity memakukannya dengan penjelasannya.
DevSolar
2
Ini adalah jawaban terbaik dan berfungsi pada BSD / OSX / GNU / Linux.
kevinarpe
1
Juga, temukan bukan satu-satunya cara untuk mendapatkan daftar file yang harus diedit secara bersamaan oleh vim. Saya dapat menggunakan grep untuk menemukan semua file dengan pola dan mencoba mengeditnya pada saat bersamaan.
Chandranshu
8

Gunakan GNU Parallel sebagai gantinya:

find . -name "*.txt" | parallel -j1 --tty vim

Atau jika Anda ingin membuka semua file dalam sekali jalan:

find . -name "*.txt" | parallel -Xj1 --tty vim

Bahkan berurusan dengan nama file seperti:

My brother's 12" records.txt

Tonton video intro untuk mempelajari lebih lanjut: http://www.youtube.com/watch?v=OpaiGYxkSuQ

Ole Tange
sumber
1
Tidak tersedia di mana-mana. Sebagian besar hari saya bekerja di server di mana saya tidak memiliki kebebasan untuk menginstal alat tambahan. Tapi terima kasih atas petunjuknya.
DevSolar
Jika Anda bebas melakukan file 'cat>; chmod + x file 'maka Anda dapat menginstal GNU Parallel: Ini hanyalah skrip perl. Jika Anda menginginkan halaman manual dan semacamnya, Anda dapat menginstalnya di bawah homedir Anda: ./configure --prefix = $ HOME && make && make install
Ole Tange
2
OK, coba itu - tetapi paralel tidak membuka semua file, itu tidak membukanya secara berurutan . Ini juga cukup sulit untuk operasi sederhana. vim $(find . -name "*.txt")lebih sederhana, dan Anda mendapatkan semua file dibuka sekaligus.
DevSolar
5
@DevSolar: Agak tidak terkait, tetapi keduanya find | xargsdan $(find)akan memiliki masalah besar dengan spasi dalam nama file.
grawity
2
@Grawity Benar, tapi tidak ada jalan mudah di sekitarnya (yang saya tahu). Anda harus mulai mengutak-atik $IFS, -print0dan sebagainya, dan kemudian Anda meninggalkan ranah solusi baris perintah sekali pakai dan mencapai titik di mana Anda harus membuat skrip ... ada alasan mengapa ruang dalam nama file tidak disarankan .
DevSolar
0

mungkin bukan yang terbaik tapi ini skrip yang saya gunakan (beri nama vim-open):

#!/usr/bin/env ruby

require 'shellwords'

inputs = (ARGV + (STDIN.tty? ? [] : STDIN.to_a)).map(&:strip)
exec("</dev/tty vim #{inputs.flatten.shelljoin}")

akan bekerja dengan vim-open a b cdan ls | vim-openmisalnya

localhostdotdev
sumber
Adapun beberapa jawaban lain, perhatikan bahwa pertanyaan yang sebenarnya adalah "mengapa", bukan "bagaimana menghindarinya". (Untuk itu saya masih akan menunjuk komentar Trevor di bawah pertanyaan saya sebagai cara paling solid yang tidak memerlukan skrip, alias atau apa pun.)
DevSolar