xargs và vi - Đầu vào không phải từ một thiết bị đầu cuối


14

Tôi có khoảng 10 php.initệp trên hệ thống của mình, được đặt ở khắp mọi nơi và tôi muốn nhanh chóng duyệt qua chúng. Tôi đã thử lệnh này:

locate php.ini | xargs vi

Nhưng vicảnh báo tôi Input is not from a terminalvà sau đó giao diện điều khiển bắt đầu trở nên thực sự kỳ lạ - sau đó tôi cần nhấn :q!để thoát vivà sau đó ngắt kết nối với phiên ssh và kết nối lại để giao diện điều khiển hoạt động bình thường trở lại.

Tôi nghĩ rằng tôi hiểu được những gì đang xảy ra ở đây - về cơ bản lệnh chưa kết thúc khi vibắt đầu nên lệnh có thể chưa kết thúc và vikhông nghĩ rằng thiết bị đầu cuối ở chế độ bình thường.

Tôi không có ý tưởng làm thế nào để sửa chữa nó. Tôi đã tìm kiếm Google và unix.stackexchange.com với may mắn.



Là một lưu ý phụ, bạn có thể chạy resetđể thiết lập lại thiết bị đầu cuối của mình khi thiết bị bị hỏng (bạn không phải ngắt kết nối với phiên ssh).
wvducky

Câu trả lời:


12
vi $(locate php.ini)

Lưu ý: điều này sẽ có vấn đề nếu đường dẫn tệp của bạn có khoảng trắng, nhưng nó có chức năng tương đương với lệnh của bạn.
Phiên bản tiếp theo này sẽ xử lý đúng không gian nhưng phức tạp hơn một chút (dòng mới trong tên tệp vẫn sẽ phá vỡ nó)

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


Giải trình:

Điều đang xảy ra là các chương trình kế thừa mô tả tệp của họ từ quá trình sinh ra chúng. xargsSTDIN của nó được kết nối với STDOUT của locate, vì vậy vikhông biết STDIN ban đầu thực sự ở đâu.


2
xargs thật tuyệt vời, một trong những công cụ yêu thích của tôi - nó không phù hợp để sử dụng với các chương trình sử dụng stdin cho bất cứ thứ gì ngoài nguồn cấp dữ liệu. tôi thích câu trả lời của bạn và lời giải thích của bạn hơn thế, vì vậy +1 dù sao đi nữa :)
cas

@CraigSanders Tôi không thích nó vì quá dễ lạm dụng (sử dụng không đúng cách) và cuối cùng phá vỡ. Tôi chưa bao giờ gặp phải bất cứ điều gì mà tôi hoàn toàn phải sử dụng xargscho điều đó không thể được thực hiện trực tiếp với vỏ (hoặc find). Tuy nhiên tôi có thể nghĩ về những trường hợp nó sẽ là giải pháp tốt nhất. Vì vậy, miễn là bạn hiểu những gì xargsđang làm, cách phân chia các đối số, cách nó chạy chương trình, v.v. và đang sử dụng nó đúng cách, tôi sẽ nói hãy thực hiện :-P
Patrick

không thể đánh bại những thứ như ... | awk '{print $3}' | xargs | sed -e 's/ /+/g' | bc(để cộng tất cả các giá trị của trường 3). hoặc với sed -e 's/ /|/g'để xây dựng một biểu thức chính quy. và vâng, giống như bất kỳ công cụ nào, bạn cần phải biết cách sử dụng nó và những hạn chế và cảnh báo của nó là gì.
cas

Cách vi $(...)tiếp cận cũng có một vấn đề với các ký tự đại diện khác zsh.
Stéphane Chazelas

Cũng lưu ý rằng với xargscách tiếp cận bên cạnh vấn đề khoảng trắng, tên tệp có dấu ngoặc đơn, dấu ngoặc kép và dấu gạch chéo ngược cũng là một vấn đề.
Stéphane Chazelas

10

Câu hỏi này trước đây đã được hỏi trên diễn đàn Super User .

Trích dẫn từ câu trả lời của @ grawity cho câu hỏi đó:

Khi bạn gọi một chương trình qua xargs, stdin (đầu vào tiêu chuẩn) của chương trình trỏ đến / dev / null. (Vì xargs không biết stdin gốc, nên nó làm điều tốt nhất tiếp theo.)

Vim hy vọng stdin của nó giống như thiết bị đầu cuối điều khiển của nó và thực hiện trực tiếp các ioctl khác nhau liên quan đến thiết bị đầu cuối. Khi được thực hiện trên / dev / null (hoặc bất kỳ mô tả tệp không tty nào), các ioctls đó là vô nghĩa và trả về ENOTTY, bị bỏ qua trong âm thầm.

Điều này được đề cập trong các trang hướng dẫn cho xarg. Từ OSX / BSD:

-o Reopen stdin as / dev / tty trong tiến trình con trước khi thực hiện lệnh. Điều này rất hữu ích nếu bạn muốn xargs chạy một ứng dụng tương tác.

Do đó, trên OSX, bạn có thể sử dụng lệnh sau:

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

Trong khi, không có công tắc trực tiếp trên phiên bản GNU, lệnh này sẽ hoạt động. (Đảm bảo bao gồm dummychuỗi, nếu không nó sẽ thả tệp đầu tiên.)

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

Các giải pháp trên là lịch sự Jaime McGuigan trên SuperUser . Thêm chúng ở đây cho bất kỳ khách truy cập trong tương lai tìm kiếm trang web cho lỗi này.


3
+1 cảm ơn vì mẹo -o. Tôi đã sử dụng xargs trong nhiều năm và không bao giờ nhận thấy rằng .... chỉ cần kiểm tra trang man trên hệ thống của tôi, đó là vì đó không phải là tính năng GNU xargs. Trang con người cung cấp xargs sh -c 'emacs "$@" < /dev/tty' emacsnhư những gì họ tuyên bố là một sự thay thế linh hoạt và di động hơn (mặc dù điều đó thật buồn cười khi GNU thích tính di động hơn các tính năng :).
cas

2

Với GNU findutilsvà shell có hỗ trợ thay thế quy trình (ksh, zsh, bash), bạn có thể thực hiện:

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

Ý tưởng là để vượt qua danh sách tập tin thông qua một -a filenamethay vì stdin. Việc sử dụng -0đảm bảo nó hoạt động bất kể các ký tự hoặc không phải ký tự mà tên tệp có thể chứa.

Với zsh, bạn có thể làm:

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

(trong đó 0cờ mở rộng tham số sẽ phân chia trên NUL).

Tuy nhiên lưu ý rằng trái với xargs -rđiều đó vẫn chạy vimà không có đối số nếu không tìm thấy tệp.


0

Chỉnh sửa nhiều php.ini trong cùng một trình soạn thảo?

Thử: vim -o $(locate php.ini)


0

Lỗi này xảy ra khi vim được gọi và nó được kết nối với đầu ra của đường ống trước đó, thay vì đầu cuối và nó nhận được đầu vào bất ngờ khác nhau (như NUL). Điều tương tự xảy ra khi bạn chạy : vim < /dev/null, vì vậy resetlệnh trong trường hợp này sẽ giúp. Điều này được giải thích tốt bởi sự tham lam tại superuser .

Trên Unix / OSX, bạn có thể sử dụng xargsvới -otham số, như:

locate php.ini | xargs -o vim

-oMở lại stdin dưới dạng / dev / tty trong tiến trình con trước khi thực hiện lệnh. Điều này rất hữu ích nếu bạn muốn xargs chạy một ứng dụng tương tác.

Trên Linux, hãy thử cách giải quyết sau:

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

Hoặc sử dụng GNU parallelthay vì xargsbuộc phân bổ tty, ví dụ:

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

Lưu ý: paralleltrên Unix / OSX sẽ không hoạt động vì nó có các tham số khác nhau và nó không hỗ trợ tty.

Nhiều lệnh phổ biến khác cũng cung cấp phân bổ giả (như -ttrong ssh), vì vậy hãy kiểm tra trợ giúp.

Hoặc sử dụng findđể chuyển tên tệp để chỉnh sửa, vì vậy không cần xargs, chỉ sử dụng -exec, ví dụ:

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

0

@ Patrick's IFShack chỉ cần thiết cho những người câm như bashzsh. fish chia chuỗi trên dòng mới theo mặc định.

$ vim (locate php.ini)

Và Chúa giúp tất cả chúng ta nếu một người trong chúng ta thực sự có một tập tin với một dòng mới trong tên của nó. Sau 17 năm sử dụng Linux, tôi đã không thấy nó dù chỉ một lần. Tôi chỉ bận tâm đến việc hỗ trợ tên tệp với dòng mới trong đó cho các tập lệnh phải hoạt động bất kể là gì, nhưng các tập lệnh như thế có thể không chạy vim tương tác.


zshchia tách trên SPC, TAB, NL và NUL theo mặc định. Điều mà nó không làm được so với việc bashthực hiện toàn cầu hóa trên kết quả để các ký tự đại diện trong tên tệp không phải là vấn đề. Trong zsh, bạn sẽ làm IFS=$'\0'; vi $(locate -0 php.ini)hoặc như tôi đã trình bày trong câu trả lời của mình vi ${(0)"$(locate -0 php.ini)"}cho một toán tử phân tách rõ ràng. Cũng lưu ý tcsh'svi "`locate php.ini`"
Stéphane Chazelas

Tào lao. OK cái này hoạt động: $ f='not there'<ret>$ ls $f<ret>nhưng cái này không: ls echo not there. OK có vẻ như tôi cần cập nhật điều này một chút.
enigmaticPhysicist

Vâng, zsh không làm điều đúng khi bạn làm ls "$(echo test; echo other test)". Chỉ có cá làm điều đúng đắn.
enigmaticPhysicist

Giả sử bạn có ý nghĩa tương tự mà không có dấu ngoặc kép, điều đó không "đúng", đó là chia tách trên các dòng, đó chỉ là một lựa chọn khác. zsh chia tách các từ theo mặc định (giống như tất cả các shell khác) và có thể được yêu cầu phân tách trên các dòng hoặc trên NUL, thông qua $IFShoặc thông qua các toán tử rõ ràng ( f0cờ mở rộng tham số). Đối với tên tệp tùy ý, việc phân tách bằng từ hoặc chia theo dòng là sai như nhau , bạn cần phải phân tách trên NUL hoặc phân tích một số mã hóa, điều này fishkhông thể thực hiện được. Trong zshđó, IFS=$'\0'; ls -ld -- $(printf '%s\0' "$file1" "$file2")hoặcls -ld -- ${(0)"$(printf '%s\0' "$file1" "$file2")"}
Stéphane Chazelas

Meh. Chia tách trên dòng mới là đủ tốt. Giống như câu trả lời, dòng mới trong tên tệp là cực kỳ hiếm. Tôi thực sự chưa bao giờ thấy nó xảy ra trong 17 năm. Và dòng mới là cách phân tách thuận tiện hơn so với nuls.
enigmaticPhysicist

0

Một cách nhanh chóng để làm điều đó, giả sử bạn có thể đảm bảo không ai trong số các đường dẫn tập tin chứa SPC, TAB, NL, *, ?, [nhân vật (hay còn \{...}trong một số vỏ) là sử dụng back-ve (aka dấu huyền) để thực hiện một lệnh trước một lệnh khác đang chạy.

Ví dụ

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

Lệnh chứa trong back-tick sẽ thực thi đầu tiên. Đầu ra của lệnh được chứa sau đó được thực thi bởi lệnh đã nêu trước dấu tick ngược.

Ví dụ, trong dòng trên, find / -type f -name 'php.ini'lệnh sẽ thực thi trước, gửi đầu ra và sau đó visẽ được thực hiện trên kết quả của split + global được áp dụng cho đầu ra đó.


3
back-ticks quá dễ bị nhầm lẫn cho dấu ngoặc đơn. sử dụng $(find ...)thay thế.
cas

1
đoán điều này cũng sẽ phá vỡ khoảng trắng và / hoặc dòng mới trong tên tệp?
cwd

Đây là cách bạn thực thi các lệnh shell trong bash scripting. Tôi chưa bao giờ có bất cứ điều gì phá vỡ trên không gian hoặc dòng mới trong tập lệnh của tôi hoặc khi sử dụng nó trong một lớp lót. Tuy nhiên, tôi chưa bao giờ thử mở nhiều tệp vibằng phương pháp này. Rất có khả năng nó có thể phá vỡ các dòng hoặc khoảng trắng mới, tùy thuộc vào cách viđọc và thực hiện đầu ra.
ngày
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.