เรียกใช้ vi ผ่าน find | xargs ทำลาย terminal ของฉัน ทำไม?


137

เมื่อเรียกใช้vimผ่านfind | xargsเช่นนี้:

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

คุณได้รับคำเตือนเกี่ยวกับ

Input is not from a terminal

และเทอร์มินัลที่มีพฤติกรรมแตกหักหลังจากนั้น ทำไมถึงเป็นอย่างนั้น?


11
หมายเหตุด้านข้าง: คุณสามารถดำเนินการนี้ได้อย่างสมบูรณ์ภายในเสียงเรียกเข้าไม่ใช้findหรือxargsเลย เปิดเป็นกลุ่มโดยไม่มีอาร์กิวเมนต์จากนั้นเรียกใช้:args **/*.txt<CR>เพื่อตั้งค่าอาร์กิวเมนต์ของกลุ่มจากภายในเครื่องมือแก้ไข
เทรเวอร์พาวเวลล์

3
@ TrevorPowell: ในทุกปีที่ผ่านมากลุ่มไม่เคยหยุดที่จะทำให้ฉันประหลาดใจ
DevSolar

ที่เกี่ยวข้อง: grep -l .. | xargs vimสร้างคำเตือนทำไม ที่ unix SE
kenorb


รายงานข้อผิดพลาด GitHub: เป็นกลุ่มไม่ได้จัดการ STDIN ชุด /
kenorb

คำตอบ:


100

เมื่อคุณเรียกใช้โปรแกรมผ่านทางxargs, stdin (input มาตรฐาน) /dev/nullของโปรแกรมชี้ไปที่ (เนื่องจาก xargs ไม่รู้จักstdin ดั้งเดิมจึงเป็นสิ่งที่ดีที่สุดถัดไป)

$ 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 คาดว่า stdin นั้นจะเหมือนกับเทอร์มินัลการควบคุมและดำเนินการกับioctlที่เกี่ยวข้องกับเทอร์มินัลต่างๆบน stdin โดยตรง เมื่อเสร็จสิ้นบน/dev/null(หรือตัวอธิบายไฟล์ที่ไม่ใช่ tty) ioctls เหล่านั้นจะไม่มีความหมายและส่งคืน ENOTTY ซึ่งจะถูกละเว้นอย่างเงียบ ๆ

  • ฉันเดาสาเหตุที่เฉพาะเจาะจงมากขึ้น: เมื่อเริ่มต้น Vim อ่านและจดจำการตั้งค่าเทอร์มินัลเก่าและเรียกคืนได้เมื่อออกจาก ในสถานการณ์ของเราเมื่อมีการร้องขอ "การตั้งค่าเก่า" สำหรับ non-tty fd (ตัวอธิบายไฟล์), Vim จะได้รับค่าทั้งหมดที่ว่างเปล่าและตัวเลือกทั้งหมดถูกปิดใช้งานและตั้งค่าเดียวกันกับเทอร์มินัลของคุณ

    คุณสามารถดูนี้โดยการทำงานvim < /dev/null, ออกจากมันแล้วทำงานsttyซึ่งจะออกเป็นจำนวนมากทั้งของ<undef>s บน Linux การรันstty saneจะทำให้เทอร์มินัลสามารถใช้งานได้อีกครั้ง (แม้ว่าตัวเลือกดังกล่าวจะหายไปเช่นiutf8อาจทำให้เกิดความรำคาญเล็กน้อยในภายหลัง)

คุณสามารถพิจารณาข้อบกพร่องนี้ใน Vim ได้เนื่องจากมันสามารถเปิด/dev/ttyสำหรับการควบคุมเทอร์มินัล แต่ไม่ (ในบางช่วงในระหว่างการเริ่มต้น Vim จะทำซ้ำ stderr ไปยัง stdin ซึ่งช่วยให้สามารถอ่านคำสั่งอินพุตของคุณ - จาก fd ที่เปิดสำหรับการเขียน - แต่แม้จะยังไม่เสร็จเร็วพอ)


20
+1 และสำหรับ TL ผู้ใช้ DR เพิ่งรันstty sane
doc_id

@hahmanisback: คำตอบอื่น ๆ รวมถึงความคิดเห็นของเทรเวอร์ทุกวิธีที่จะหลีกเลี่ยงการแตกขั้วในตอนแรก ฉันยอมรับคำตอบของ grawity เพราะคำถามของฉันคือ "ทำไม" ไม่ใช่ "ทำอย่างไรจึงจะหลีกเลี่ยง" - นั่นเป็นคำถามอีกข้อที่วางไข่นี้
DevSolar

@DevSolar เข้าใจ แต่คิดเกี่ยวกับคนที่ผิดหวังเช่นฉันที่ google วิธีการกำจัดพฤติกรรมที่ไม่น่าเสียดาย - มีเวลาพอที่จะศึกษา "ทำไม" ซึ่งเป็นที่น่าสนใจมากอย่างไรก็ตาม
doc_id

4
เมื่อเครื่องของฉันหยุดพักแบบนี้ฉันก็ใช้resetแทนstty saneและมันก็ใช้ได้ดีหลังจากนั้น
Capi Etheriel

136

(ต่อจากคำอธิบายของ grawity xargsชี้stdinไปที่/dev/null)

วิธีการแก้ปัญหานี้คือการเพิ่มพารามิเตอร์-o xargsจากman xargs:

-o

      เปิด stdin อีกครั้งเช่นเดียวกับ/dev/ttyในกระบวนการลูกก่อนดำเนินการคำสั่ง สิ่งนี้มีประโยชน์หากคุณต้องการxargsเรียกใช้แอปพลิเคชันแบบโต้ตอบ

ดังนั้นบรรทัดของรหัสต่อไปนี้จะทำงานให้คุณ:

หา -name "* .txt" | xargs -o vim

GNU xargs สนับสนุนส่วนขยายนี้ตั้งแต่มีการเปิดตัวบางส่วนในปี 2560 (ด้วยชื่อตัวเลือกแบบยาว--open-tty)

สำหรับรุ่นเก่าหรือรุ่นอื่น ๆ คุณสามารถส่ง/dev/ttyต่อเพื่อแก้ไขปัญหาได้อย่างชัดเจน:

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

(ที่ignoremeนั่นจะใช้เวลาถึง $ 0 ดังนั้น $ @ เป็นอาร์กิวเมนต์ทั้งหมดจาก xargs.)


2
คุณจะสร้างนามแฝง bash จากสิ่งนี้ได้อย่างไร $@ดูเหมือนจะแปลข้อโต้แย้งไม่ถูกต้อง
zanegray

1
@zanegray - คุณไม่สามารถสร้างชื่อแทนได้ แต่คุณสามารถทำให้เป็นฟังก์ชันได้ ลอง:function vimin () { xargs sh -c 'vim "$@" < /dev/tty' vim; }
คริสโตเฟอร์

สำหรับคำอธิบายโดยละเอียดเกี่ยวกับวิธีการทำงานของโซลูชัน GNU xargs และสาเหตุที่คุณต้องใช้ignoremeสตริงจำลองดูvi.stackexchange.com/a/17813
wisbucky

@zanegray คุณสามารถทำให้เป็นนามแฝงได้ คำพูดที่ยุ่งยาก ดูวิธีการแก้ปัญหาได้ที่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.(มันไม่สามารถใช้ได้กับ macOS สำหรับฉันเพราะฉันติดตั้ง xargs จาก homebrew (หนึ่ง GNU))
localhostdotdev

33

วิธีที่ง่ายที่สุด:

vim $(find . -name "*foo*")

5
คำถามหลักคือ "ทำไม" ไม่ใช่ "ทำอย่างไรจึงจะหลีกเลี่ยงได้" และได้รับคำตอบว่าพอใจเมื่อสองปีครึ่งที่แล้ว
DevSolar

5
แน่นอนว่าสิ่งนี้ไม่ทำงานอย่างถูกต้องเมื่อชื่อไฟล์มีช่องว่างหรืออักขระพิเศษอื่น ๆ และยังมีความเสี่ยงด้านความปลอดภัย
Dejay Clayton

1
คำตอบที่ฉันชอบเพราะมันใช้ได้กับทุกคำสั่งที่แสดงรายการไฟล์ไม่ใช่แค่ "ค้นหา" หรือสัญลักษณ์แทน มันต้องใช้ความไว้วางใจเล็กน้อยเนื่องจาก Dejay ชี้ให้เห็น
Travis Wilson

1
สิ่งนี้จะไม่ทำงานกับการใช้งานหลายกรณี xargs ถูกออกแบบมาเพื่อ: เช่นเมื่อจำนวนเส้นทางสูงมาก (cc @TravisWilson)
บุคคลที่ดี

21

มันควรจะทำงานได้ดีถ้าคุณใช้ตัวเลือก -exec ในการค้นหามากกว่าการไพพ์ลงใน xargs

find . -type f -name filename.txt -exec vi {} + 

2
อืมมม ... เคล็ดลับมี+(แทนที่จะเป็น "ปกติ" \;) เพื่อให้ได้ไฟล์ทั้งหมดที่พบลงในหนึ่งเซสชั่นที่เป็นกลุ่ม - ตัวเลือกฉันให้ลืมเกี่ยวกับ คุณถูกต้องแน่นอนและ +1 สำหรับสิ่งนั้น ฉันใช้vim $(find ...)เพียงนิสัย อย่างไรก็ตามฉันถามว่าทำไมการทำงานของท่อถึงขั้วและการตอกตะปูนั้นด้วยคำอธิบายของเขา
DevSolar

2
นี่เป็นคำตอบที่ดีที่สุดและใช้ได้กับทั้ง BSD / OSX / GNU / Linux
kevinarpe

1
นอกจากนี้ find ไม่ได้เป็นวิธีเดียวในการรับรายการไฟล์ที่ต้องแก้ไขพร้อมกันโดย vim ฉันสามารถใช้ grep เพื่อค้นหาไฟล์ทั้งหมดที่มีรูปแบบและลองแก้ไขในเวลาเดียวกันได้เช่นกัน
Chandranshu

8

ใช้ GNU Parallel แทน:

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

หรือถ้าคุณต้องการเปิดไฟล์ทั้งหมดในครั้งเดียว:

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

มันเกี่ยวข้องกับชื่อไฟล์อย่างถูกต้องเช่น:

My brother's 12" records.txt

ดูวิดีโอแนะนำเพื่อเรียนรู้เพิ่มเติม: http://www.youtube.com/watch?v=OpaiGYxkSuQ


1
ไม่สามารถใช้งานได้ทั่วไป ส่วนใหญ่ฉันทำงานกับเซิร์ฟเวอร์ที่ฉันไม่มีอิสระในการติดตั้งเครื่องมือเพิ่มเติม แต่ขอบคุณสำหรับคำใบ้ต่อไป
DevSolar

หากคุณมีอิสระที่จะทำ 'cat> file; chmod + x file 'จากนั้นคุณสามารถติดตั้ง GNU Parallel: เป็นเพียงสคริปต์ Perl หากคุณต้องการหน้าคนและคุณสามารถติดตั้งภายใต้ homedir ของคุณ: ./configure --prefix = $ HOME && & ทำการติดตั้ง
Ole Tange

2
ตกลงพยายามที่ - แต่ขนานไม่ได้เปิดไฟล์ทั้งหมดที่มันจะเปิดพวกเขาอย่างต่อเนื่อง นอกจากนี้ยังเป็นคำที่ใช้ง่าย vim $(find . -name "*.txt")ง่ายขึ้นและคุณเปิดไฟล์ทั้งหมดพร้อมกัน
DevSolar

5
@DevSolar: ค่อนข้างไม่เกี่ยวข้อง แต่ทั้งคู่find | xargsและ$(find)จะมีปัญหาใหญ่กับช่องว่างในชื่อไฟล์
grawity

2
@grawity ถูกต้อง แต่ไม่มีวิธีที่ง่ายรอบ ๆ มัน (ที่ฉันรู้) คุณจะต้องเริ่มต้นการเล่นซอกับ$IFS, -print0และสิ่งแล้วคุณซ้ายขอบเขตของการแก้ปัญหาบรรทัดคำสั่งหนึ่งยิงและถึงจุดที่คุณจะมากับสคริปต์ ... มีเหตุผลว่าทำไมช่องว่างในชื่อไฟล์เป็นกำลังใจ .
DevSolar

0

อาจจะไม่ดีที่สุด แต่นี่เป็นสคริปต์ที่ฉันใช้ (ตั้งชื่อvim-open):

#!/usr/bin/env ruby

require 'shellwords'

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

จะทำงานร่วมกับvim-open a b cและls | vim-openตัวอย่างเช่น


สำหรับคำตอบอื่น ๆ โปรดทราบว่าคำถามที่แท้จริงคือ "ทำไม" ไม่ใช่ "วิธีหลีกเลี่ยง" (ซึ่งฉันจะยังคงชี้ไปที่ความคิดเห็นของเทรเวอร์ภายใต้คำถามของฉันเป็นวิธีที่มั่นคงที่สุดที่ไม่จำเป็นต้องใช้สคริปต์นามแฝงหรืออะไรก็ตาม)
DevSolar
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.