xargs dan vi - “Input bukan dari terminal”

14

Saya memiliki sekitar 10 php.inifile di sistem saya, yang terletak di semua tempat, dan saya ingin menelusuri dengan cepat. Saya mencoba perintah ini:

locate php.ini | xargs vi

Tetapi vimemperingatkan saya Input is not from a terminaldan kemudian konsol mulai menjadi sangat aneh - setelah itu saya perlu menekan :q!untuk berhenti vidan kemudian lepaskan dari sesi ssh dan sambungkan kembali agar konsol berperilaku normal lagi.

Saya pikir saya agak mengerti apa yang terjadi di sini - pada dasarnya perintah belum selesai ketika vidimulai sehingga perintah mungkin belum selesai dan vitidak berpikir bahwa terminal dalam mode normal.

Saya tidak tahu bagaimana cara memperbaikinya. Saya telah mencari Google dan juga unix.stackexchange.com dengan nasib buruk.

cwd
sumber
Sebagai catatan tambahan, Anda dapat menjalankan resetuntuk mereset terminal Anda ketika sedang kacau (Anda tidak harus memutuskan sambungan dari sesi ssh).
wisbucky

Jawaban:

12
vi $(locate php.ini)

Catatan: ini akan memiliki masalah jika path file Anda memiliki spasi, tetapi secara fungsional setara dengan perintah Anda.
Versi berikutnya ini akan menangani spasi dengan benar tetapi sedikit lebih rumit (baris baru dalam nama file akan tetap memecahnya)

(IFS=$'\n'; vi $(locate php.ini))


Penjelasan:

Apa yang terjadi adalah bahwa program mewarisi deskriptor file mereka dari proses yang menelurkan mereka. xargsmemiliki STDIN yang terhubung ke STDOUT locate, jadi vitidak tahu apa STDIN asli sebenarnya.

Patrick
sumber
2
xargs luar biasa, salah satu alat favorit saya - itu hanya tidak cocok untuk digunakan dengan program yang menggunakan stdin untuk apa pun selain umpan data. saya suka jawaban Anda dan penjelasan Anda selain itu, jadi +1 tetap :)
cas
@CraigSanders Saya tidak suka karena terlalu mudah untuk disalahgunakan (tidak digunakan dengan benar) dan akhirnya rusak. Saya tidak pernah mengalami sesuatu yang benar-benar harus saya gunakan xargsuntuk itu tidak dapat dilakukan secara langsung dengan shell (atau find). Namun saya bisa memikirkan kasus di mana itu akan menjadi solusi terbaik. Jadi, selama Anda memahami apa xargsyang dilakukan, bagaimana ia memecah argumen, bagaimana menjalankan program, dll, dan menggunakannya dengan benar, saya akan mengatakan untuk itu :-P
Patrick
tidak dapat dikalahkan untuk hal-hal seperti ... | awk '{print $3}' | xargs | sed -e 's/ /+/g' | bc(untuk menambahkan semua nilai-nilai bidang 3). atau dengan sed -e 's/ /|/g'membangun regexp. dan ya, seperti alat apa pun, Anda perlu tahu cara menggunakannya dan apa batasan dan peringatannya.
cas
The vi $(...)Pendekatan juga memiliki masalah dengan wildcard pada kulit selain zsh.
Stéphane Chazelas
Juga perhatikan bahwa dengan xargspendekatan di samping masalah spasi putih, nama file dengan tanda kutip tunggal, tanda kutip ganda dan garis miring terbalik juga merupakan masalah.
Stéphane Chazelas
10

Pertanyaan ini sebelumnya telah ditanyakan di forum Pengguna Super .

Mengutip dari jawaban @ grawity pada pertanyaan itu:

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

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.

Ini disebutkan di halaman manual untuk xarg. Dari OSX / BSD:

-o Buka kembali stdin sebagai / dev / tty dalam proses anak sebelum menjalankan perintah. Ini berguna jika Anda ingin xargs menjalankan aplikasi interaktif.

Karenanya, pada OSX, Anda dapat menggunakan perintah berikut:

find . -name "php.ini" | xargs -o vim

Sementara, tidak ada saklar langsung pada versi GNU, perintah ini akan berfungsi. (Pastikan untuk memasukkan dummystring, jika tidak akan menjatuhkan file pertama.)

find . -name "php.ini" | xargs bash -c '</dev/tty vim "$@"' dummy

Solusi di atas adalah milik Jaime McGuigan di SuperUser . Menambahkannya di sini untuk pengunjung masa depan yang mencari situs untuk kesalahan ini.

darnir
sumber
3
+1 terima kasih atas tipnya. Saya telah menggunakan xargs selama bertahun-tahun dan tidak pernah memperhatikan bahwa .... baru saja memeriksa halaman manual pada sistem saya, itu karena itu bukan fitur GNU xargs. Halaman manual memang menyediakan xargs sh -c 'emacs "$@" < /dev/tty' emacsapa yang mereka klaim sebagai alternatif yang lebih fleksibel dan portabel (meskipun agak lucu bagi GNU untuk lebih memilih portabilitas daripada fitur :).
cas
2

Dengan GNU findutils, dan sebuah shell dengan dukungan untuk substitusi proses (ksh, zsh, bash), Anda dapat melakukan:

xargs -r0a <(locate -0 php.ini) vi

Idenya adalah untuk lulus daftar file melalui -a filenamebukan stdin. Menggunakan -0memastikan itu berfungsi terlepas dari apa karakter atau non-karakter nama file mungkin berisi.

Dengan zsh, Anda bisa melakukan:

vi ${(0)"$(locate -0 php.ini)"}

(di mana 0bendera ekspansi parameter untuk dibagi pada NUL).

Namun perhatikan bahwa bertentangan dengan xargs -ritu masih berjalan vitanpa argumen jika tidak ada file yang ditemukan.

Stéphane Chazelas
sumber
0

Edit beberapa php.ini dalam editor yang sama?

Mencoba: vim -o $(locate php.ini)

bunga aster
sumber
0

Kesalahan ini terjadi ketika vim dipanggil dan terhubung ke output pipa sebelumnya, bukan terminal dan menerima input yang berbeda yang tidak terduga (seperti NUL). Hal yang sama terjadi ketika Anda menjalankan :, vim < /dev/nulljadi resetperintah dalam hal ini membantu. Ini dijelaskan dengan baik oleh grawity di superuser .

Pada Unix / OSX Anda dapat menggunakan xargsdengan -oparameter, seperti:

locate php.ini | xargs -o vim

-oBuka kembali stdin sebagai / dev / tty dalam proses anak sebelum menjalankan perintah. Ini berguna jika Anda ingin xargs menjalankan aplikasi interaktif.

Di Linux coba solusi berikut ini:

locate php.ini | xargs -J% sh -c 'vim < /dev/tty $@'

Atau gunakan GNU parallelalih-alih xargsuntuk memaksa alokasi tty, misalnya:

locate php.ini | parallel -X --tty vi

Catatan: parallelpada Unix / OSX tidak akan berfungsi karena memiliki parameter yang berbeda dan tidak mendukung tty.

Banyak perintah populer lainnya menyediakan alokasi pseudo-tty juga (seperti -tdalam ssh), jadi periksa bantuan.

Atau gunakan finduntuk meneruskan nama file untuk diedit, jadi tidak perlu xargs, cukup gunakan -exec, misalnya:

find /etc -name php.ini -exec vim {} +
kenorb
sumber
0

@ Patrick IFSretas hanya diperlukan untuk kerang bodoh seperti bashdan zsh. fish memisahkan string pada baris baru secara default.

$ vim (locate php.ini)

Dan Tuhan membantu kita semua jika salah satu dari kita benar-benar memiliki file dengan baris baru dalam namanya. Setelah 17 tahun menggunakan Linux, saya belum pernah melihatnya sekali pun. Saya hanya akan bersusah payah mendukung nama file dengan baris baru di dalamnya untuk skrip yang harus bekerja apa pun yang terjadi, tetapi skrip seperti itu mungkin tidak menjalankan vim secara interaktif.

Ahli Fisika enigmatic
sumber
zshmemisahkan SPC, TAB, NL dan NUL secara default. Hal yang tidak dilakukan dibandingkan bashadalah melakukan globbing pada hasilnya sehingga karakter wildcard dalam nama file tidak menjadi masalah. Di zsh, Anda akan melakukan IFS=$'\0'; vi $(locate -0 php.ini)atau seperti yang saya tunjukkan dalam jawaban saya vi ${(0)"$(locate -0 php.ini)"}untuk operator pemisahan yang eksplisit. Perhatikan juga tcsh'svi "`locate php.ini`"
Stéphane Chazelas
aw, sial. OK ini berfungsi: $ f='not there'<ret>$ ls $f<ret>tetapi ini tidak: ls echo not there. OK sepertinya saya perlu memperbarui ini sedikit.
EnigmaticPhysicist
Ya, zsh tidak melakukan hal yang benar ketika Anda melakukannya ls "$(echo test; echo other test)". Hanya ikan yang melakukan hal yang benar.
EnigmaticPhysicist
Anggap saja maksud Anda sama tanpa tanda kutip, itu bukan "benar", yang terpecah-pecah, itu hanya pilihan yang berbeda. zsh membagi kata-kata secara default (seperti semua shell lainnya) dan dapat dikatakan untuk dipisah-pisah di baris atau di NUL, baik melalui $IFSatau melalui operator eksplisit ( fdan 0flag ekspansi parameter). Untuk nama file yang sewenang-wenang, membelah dengan kata atau membelah dengan baris sama-sama salah , Anda perlu memecah pada NUL atau parsing beberapa pengkodean, yang fishtidak bisa dilakukan. In zsh, itu IFS=$'\0'; ls -ld -- $(printf '%s\0' "$file1" "$file2")atauls -ld -- ${(0)"$(printf '%s\0' "$file1" "$file2")"}
Stéphane Chazelas
Ah. Membagi baris baru cukup baik. Seperti jawabannya, baris baru dalam nama file sangat jarang. Saya benar-benar tidak pernah melihat itu terjadi dalam 17 tahun. Dan baris baru adalah pemisah yang jauh lebih nyaman daripada inti.
EnigmaticPhysicist
0

Cara cepat untuk melakukannya, dengan asumsi Anda dapat menjamin tidak ada path file berisi SPC, TAB, NL, *, ?, [karakter (juga \dan {...}dalam beberapa kerang) adalah dengan menggunakan back-kutu (alias aksen kuburan) untuk menjalankan perintah sebelum perintah lain berjalan.

Misalnya

vi `find / -type f -name 'php.ini'`

Perintah yang terkandung dalam back-ticks akan dieksekusi terlebih dahulu. Output dari perintah yang terkandung kemudian dieksekusi oleh perintah yang dinyatakan sebelum tick-back.

Sebagai contoh, pada baris di atas, find / -type f -name 'php.ini'perintah akan dieksekusi terlebih dahulu, kirim output, dan kemudian viakan dieksekusi pada hasil split + glob yang diterapkan pada output itu.

tacotuesday
sumber
3
tick-back terlalu mudah dikacaukan untuk single-quotes. gunakan $(find ...)saja.
cas
1
menebak ini juga akan memecah spasi dan / atau baris baru dalam nama file?
cwd
Ini adalah bagaimana Anda menjalankan perintah shell dalam skrip bash. Saya tidak pernah mengalami kerusakan pada spasi atau baris baru di skrip saya atau saat menggunakannya dalam satu baris. Namun, saya belum pernah mencoba membuka banyak file dalam vimenggunakan metode ini. Sangat mungkin bisa rusak pada baris atau spasi baru, tergantung pada bagaimana vimembaca dan menjalankan output.
tacotuesday