Làm cách nào tôi có thể sử dụng xargs để sao chép các tệp có dấu cách và dấu ngoặc kép trong tên của chúng?


232

Tôi đang cố gắng sao chép một loạt các tệp bên dưới một thư mục và một số tệp có dấu cách và dấu ngoặc đơn trong tên của chúng. Khi tôi cố gắng chuỗi lại với nhau findgrepvới xargs, tôi nhận được lỗi sau:

find .|grep "FooBar"|xargs -I{} cp "{}" ~/foo/bar
xargs: unterminated quote

Bất kỳ đề xuất cho việc sử dụng xargs mạnh mẽ hơn?

Đây là trên Mac OS X 10.5.3 (Leopard) với BSD xargs.


2
Thông báo lỗi GNU xargs cho điều này với một tên tệp chứa một trích dẫn duy nhất hữu ích hơn: "xargs: trích dẫn đơn chưa từng có; theo trích dẫn mặc định là đặc biệt đối với xargs trừ khi bạn sử dụng tùy chọn -0".
Steve Jessop

3
GNU xargs cũng có --delimitertùy chọn ( -d). Hãy thử với \ntư cách là dấu phân cách, Điều này ngăn không cho xargstách các dòng có khoảng trắng thành nhiều từ / đối số.
MattBianco

Câu trả lời:


199

Bạn có thể kết hợp tất cả những điều đó thành một findlệnh duy nhất :

find . -iname "*foobar*" -exec cp -- "{}" ~/foo/bar \;

Điều này sẽ xử lý tên tệp và thư mục có không gian trong đó. Bạn có thể sử dụng -nameđể có được kết quả phân biệt chữ hoa chữ thường.

Lưu ý: --Cờ được chuyển để cpngăn nó xử lý các tệp bắt đầu bằng -tùy chọn.


70
Mọi người sử dụng xargs vì thông thường sẽ nhanh hơn khi gọi 5 lần thực thi với 200 đối số mỗi lần so với gọi 1000 lần với một đối số mỗi lần.
tzot

12
Câu trả lời từ Chris Jester-Young phải là "câu trả lời tốt" ở đó ... BTW giải pháp này không hoạt động nếu tên tệp bắt đầu bằng "-". Ít nhất, nó cần "-" sau cp.
Keltia

11
Ví dụ về tốc độ - hơn 829 tệp, phương thức "find -exec" mất 26 giây trong khi công cụ phương thức "find -print0 | xargs --null" 0,7 giây. Sự khác biệt đáng kể.
Peter Porter

7
@tzot Một bình luận muộn nhưng dù sao, xargskhông bắt buộc phải giải quyết vấn đề bạn đang mô tả, findđã hỗ trợ nó với -exec +dấu câu.
jlliagre

3
không trả lời câu hỏi làm thế nào để đối phó với không gian
Ben Glasser

117

find . -print0 | grep --null 'FooBar' | xargs -0 ...

Tôi không biết về việc grephỗ trợ --null, cũng không cho dù xargssự ủng hộ -0, trên Leopard, nhưng trên GNU đó là tất cả tốt.


1
Leopard không hỗ trợ "-Z" (đó là GNU grep) và tất nhiên là tìm (1) và xargs (1) hỗ trợ "-0".
Keltia

1
Trên OS X 10.9 grep -{z|Z}có nghĩa là "hành xử như zgrep" (giải nén) và không có ý định "in một byte bằng 0 sau mỗi tên tệp". Sử dụng grep --nullđể đạt được sau này.
bassim

4
Có chuyện gì với bạn find . -name 'FooBar' -print0 | xargs -0 ...vậy?
Quentin Pradet

1
@QuentinPradet Rõ ràng, đối với một chuỗi cố định như "FooBar" -namehoặc -pathhoạt động tốt. OP đã chỉ định sử dụng grep, có lẽ vì họ muốn lọc danh sách bằng các biểu thức thông thường.
Chris Jester-Young

1
@ Hi-Angel Đó chính xác là lý do tại sao tôi sử dụng xargs -0 kết hợp với find -print0 . Cái sau in tên tập tin với một bộ kết thúc NUL và cái trước nhận các tập tin theo cách đó. Tại sao? Tên tệp trong Unix có thể chứa các ký tự dòng mới. Nhưng chúng không thể chứa các ký tự NUL.
Chris Jester-Young

92

Cách dễ nhất để làm những gì người đăng ban đầu muốn là thay đổi dấu phân cách từ bất kỳ khoảng trắng nào thành chỉ ký tự cuối dòng như thế này:

find whatever ... | xargs -d "\n" cp -t /var/tmp

4
Anwser này đơn giản, hiệu quả và đi thẳng vào vấn đề: bộ phân cách mặc định được đặt cho xargs quá rộng và cần được thu hẹp cho những gì OP muốn làm. Tôi biết điều này trực tiếp bởi vì tôi đã gặp phải vấn đề chính xác tương tự ngày hôm nay khi làm điều gì đó tương tự, ngoại trừ trong cygwin. Nếu tôi đã đọc trợ giúp cho lệnh xargs, tôi có thể đã tránh được một vài cơn đau đầu, nhưng giải pháp của bạn đã khắc phục nó cho tôi. Cảm ơn ! (Vâng, OP đã sử dụng MacOS bằng BSD xargs mà tôi không sử dụng, nhưng tôi hy vọng rằng tham số "-d" của xargs tồn tại trong tất cả các phiên bản).
Etienne Delavennat

7
Câu trả lời hay nhưng không hoạt động trên Mac. Thay vào đó chúng ta có thể đường ống tìm vào sed -e 's_\(.*\)_"\1"_g'để dấu ngoặc kép lực lượng xung quanh tên file
ishahak

10
Đây phải là câu trả lời được chấp nhận. Câu hỏi là về việc sử dụng xargs.
Mohammad Alhashash

2
Tôi nhận đượcxargs: illegal option -- d
nehem

1
Thật đáng để chỉ ra rằng tên tệp có thể chứa một ký tự dòng mới trên nhiều hệ thống * nix. Bạn không bao giờ có thể gặp phải điều này trong tự nhiên, nhưng nếu bạn đang chạy các lệnh shell trên đầu vào không tin cậy thì đây có thể là một vấn đề đáng lo ngại.
Soren Bjornstad

71

Điều này hiệu quả hơn vì nó không chạy "cp" nhiều lần:

find -name '*FooBar*' -print0 | xargs -0 cp -t ~/foo/bar

1
Điều này đã không làm việc cho tôi. Nó đã cố gắng cp ~ / foo / bar vào bất cứ thứ gì bạn tìm thấy, nhưng không phải ngược lại
Shervin Asgari

13
Cờ -t thành cp là một phần mở rộng GNU, AFAIK và không có sẵn trên OS X. Nhưng nếu có, nó sẽ hoạt động như trong câu trả lời này.
metamatt

2
Tôi đang sử dụng Linux. Cảm ơn về công tắc '-t'. Đó là những gì tôi đã thiếu :-)
Vahid Pazirandeh

59

Tôi gặp vấn đề tương tự. Đây là cách tôi giải quyết nó:

find . -name '*FoooBar*' | sed 's/.*/"&"/' | xargs cp ~/foo/bar

Tôi đã từng sedthay thế từng dòng đầu vào bằng cùng một dòng, nhưng được bao quanh bởi dấu ngoặc kép. Từ sedtrang man, " ... Một dấu và (` `& '') xuất hiện trong thay thế được thay thế bằng chuỗi khớp với RE ... " - trong trường hợp này, .*toàn bộ dòng.

Điều này giải quyết xargs: unterminated quotelỗi.


3
Tôi đang ở trên windows và sử dụng gnuwin32, vì vậy tôi phải sử dụng sed s/.*/\"&\"/để làm cho nó hoạt động.
Pat

Có nhưng có lẽ điều này sẽ không xử lý tên tập tin "trong - trừ khi sed cũng trích dẫn trích dẫn?
artfulrobot

Sử dụng sedlà thiên tài và bây giờ là giải pháp chính xác mà không cần viết lại vấn đề!
entonio

53

Phương pháp này hoạt động trên Mac OS X v10.7.5 (Lion):

find . | grep FooBar | xargs -I{} cp {} ~/foo/bar

Tôi cũng đã kiểm tra cú pháp chính xác mà bạn đã đăng. Điều đó cũng hoạt động tốt vào ngày 10.7.5.


4
Điều này hoạt động, nhưng -Ingụ ý -L 1(vì vậy, hướng dẫn sử dụng), có nghĩa là lệnh cp đang được chạy một lần trên mỗi tệp = v chậm.
artfulrobot

xargs -J% cp% <đích dir> Có thể hiệu quả hơn trên OSX.
Walker D

3
Xin lỗi, nhưng đây là SAI. Đầu tiên, nó tạo ra chính xác lỗi mà TO muốn tránh. Bạn phải sử dụng find ... -print0xargs -0để làm việc arround xargs "theo mặc định là đặc biệt". Thứ hai, thường sử dụng '{}'không {}trong các lệnh được truyền cho xargs, để bảo vệ chống lại khoảng trắng và ký tự đặc biệt.
Andreas Spindler

3
Xin lỗi Andreas Spindler, tôi không quen với xargs và tìm thấy dòng này sau một số thử nghiệm. Nó dường như làm việc cho hầu hết những người đã bình luận về nó và nâng cao nó. Bạn có phiền đi sâu vào chi tiết hơn một chút về loại lỗi mà nó tạo ra không? Ngoài ra, bạn có phiền đăng bài đầu vào chính xác mà bạn nghĩ sẽ chính xác hơn không? Cảm ơn bạn.
the_minted 20/03/2016

12

Chỉ không sử dụng xargs. Đây là một chương trình gọn gàng nhưng nó không phù hợp findkhi gặp các trường hợp không tầm thường.

Đây là một di động (POSIX) giải pháp, tức là một mà không yêu cầu find, xargshoặc cpmở rộng cụ GNU:

find . -name "*FooBar*" -exec sh -c 'cp -- "$@" ~/foo/bar' sh {} +

Lưu ý kết thúc +thay vì thông thường hơn ;.

Giải pháp này:

  • xử lý chính xác các tập tin và thư mục với các không gian nhúng, dòng mới hoặc bất kỳ ký tự kỳ lạ nào.

  • hoạt động trên mọi hệ thống Unix và Linux, ngay cả những hệ thống không cung cấp bộ công cụ GNU.

  • không sử dụng xargsđó là một chương trình hay và hữu ích, nhưng đòi hỏi quá nhiều tinh chỉnh và các tính năng không chuẩn để xử lý findđầu ra đúng cách .

  • cũng hiệu quả hơn (đọc nhanh hơn ) so với chấp nhận và hầu hết nếu không phải là tất cả các câu trả lời khác.

Cũng lưu ý rằng mặc dù những gì được nêu trong một số phản hồi hoặc bình luận trích dẫn khác {}là vô ích (trừ khi bạn đang sử dụng fishvỏ kỳ lạ ).



1
@PeterMortensen Có lẽ bạn bỏ qua dấu cộng. findcó thể làm những gì xargsmà không cần bất kỳ chi phí nào.
jlliagre

8

Xem xét bằng cách sử dụng tùy chọn dòng lệnh --null cho xargs với tùy chọn -print0 đang tìm.


8

Đối với những người dựa vào các lệnh, ngoài tìm kiếm, ví dụ ls:

find . | grep "FooBar" | tr \\n \\0 | xargs -0 -I{} cp "{}" ~/foo/bar

1
Hoạt động nhưng chậm vì -Ingụ ý-L 1
artfulrobot

6
find | perl -lne 'print quotemeta' | xargs ls -d

Tôi tin rằng điều này sẽ hoạt động đáng tin cậy cho bất kỳ nhân vật nào ngoại trừ nguồn cấp dữ liệu (và tôi nghi ngờ rằng nếu bạn có nguồn cấp dữ liệu trong tên tệp của mình, thì bạn đã gặp vấn đề tồi tệ hơn thế này). Nó không yêu cầu công cụ tìm kiếm GNU, chỉ cần Perl, vì vậy nó sẽ hoạt động khá nhiều ở mọi nơi.


Có thể có một nguồn cấp dữ liệu trong một tên tệp? Chưa bao giờ nghe về nó.
mtk

2
Quả thực là như vậy. Hãy thử, ví dụ:mkdir test && cd test && perl -e 'open $fh, ">", "this-file-contains-a-\n-here"' && ls | od -tx1
mavit

1
|perl -lne 'print quotemeta'chính xác là những gì tôi đã tìm kiếm. Các bài đăng khác ở đây không giúp tôi vì thay vì findtôi cần sử dụng grep -rlđể giảm đáng kể số lượng tệp PHP xuống chỉ những tệp bị nhiễm phần mềm độc hại.
Marcos

perl và quoteemeta chung hơn nhiều so với print0 / -0 - cảm ơn vì giải pháp chung cho các tập tin đường ống có khoảng trắng
bmike

5

Tôi đã thấy rằng cú pháp sau đây hoạt động tốt cho tôi.

find /usr/pcapps/ -mount -type f -size +1000000c | perl -lpe ' s{ }{\\ }g ' | xargs ls -l | sort +4nr | head -200

Trong ví dụ này, tôi đang tìm 200 tệp lớn nhất trên 1.000.000 byte trong hệ thống tệp được gắn tại "/ usr / pcapps".

Dòng lót Perl giữa "find" và "xargs" thoát / trích dẫn mỗi khoảng trống để "xargs" chuyển bất kỳ tên tệp nào có khoảng trống được nhúng sang "ls" dưới dạng một đối số.


3

Thử thách khung hình - bạn đang hỏi cách sử dụng xargs. Câu trả lời là: bạn không sử dụng xargs, vì bạn không cần nó.

Các bình luận bằng cáchuser80168 mô tả một cách để làm điều này trực tiếp với cp, mà không gọi cp cho mỗi tập tin:

find . -name '*FooBar*' -exec cp -t /tmp -- {} +

Điều này hoạt động vì:

  • các cp -tlá cờ cho phép để cung cấp cho các mục đích gần đầu cp, chứ không phải là gần kết thúc. Từ man cp:
   -t, --target-directory=DIRECTORY
         copy all SOURCE arguments into DIRECTORY
  • Các --lá cờ nói cpđể giải thích tất cả mọi thứ sau như một tên tập tin, không phải là một lá cờ, vì vậy các files bắt đầu với -hoặc --không nhầm lẫn cp; bạn vẫn cần điều này bởi vì -/ các --ký tự được diễn giải bởi cp, trong khi bất kỳ ký tự đặc biệt nào khác được giải thích bởi shell.

  • Các find -exec command {} +biến thể về cơ bản làm giống như xargs. Từ man find:

   -exec command {} +                                                     
         This  variant  of the -exec action runs the specified command on
         the selected files, but the command line is built  by  appending
         each  selected file name at the end; the total number of invoca‐
         matched  files.   The command line is built in much the same way
         that xargs builds its command lines.  Only one instance of  `{}'
         is  allowed  within the command, and (when find is being invoked
         from a shell) it should be quoted (for example, '{}') to protect
         it  from  interpretation  by shells.  The command is executed in
         the starting directory.  If any invocation  returns  a  non-zero
         value  as exit status, then find returns a non-zero exit status.
         If find encounters an error, this can sometimes cause an immedi‐
         ate  exit, so some pending commands may not be run at all.  This
         variant of -exec always returns true.

Bằng cách sử dụng trực tiếp trong tìm kiếm, điều này tránh được sự cần thiết của một đường ống hoặc một lời gọi vỏ, do đó bạn không cần phải lo lắng về bất kỳ ký tự khó chịu nào trong tên tệp.


Tìm thấy tuyệt vời, tôi không có ý tưởng !!! "-exec Utility [argument ...] {} + Giống như -exec, ngoại trừ việc` `{} '' được thay thế bằng càng nhiều tên đường dẫn càng tốt cho mỗi lần gọi tiện ích. Hành vi này tương tự như của xargs (1 ). " trong việc thực hiện BSD.
Conny

2

Xin lưu ý rằng hầu hết các tùy chọn được thảo luận trong các câu trả lời khác không phải là tiêu chuẩn trên các nền tảng không sử dụng các tiện ích GNU (ví dụ Solaris, AIX, HP-UX). Xem thông số kỹ thuật POSIX để biết hành vi xargs 'tiêu chuẩn'.

Tôi cũng thấy hành vi của xargs theo đó nó chạy lệnh ít nhất một lần, thậm chí không có đầu vào, gây phiền toái.

Tôi đã viết phiên bản riêng của xargs (xargl) để xử lý các vấn đề về khoảng trắng trong tên (chỉ có dòng mới riêng biệt - mặc dù kết hợp 'find ... -print0' và 'xargs -0' khá gọn gàng vì tên tệp không thể chứa các ký tự ASCII NUL '\ 0'. Xargl của tôi không đầy đủ như nó cần phải có giá trị xuất bản - đặc biệt là vì GNU có các phương tiện ít nhất là tốt.


2
GitHub hoặc điều đó đã không xảy ra
Corey Goldberg

@CoreyGoldberg: Tôi đoán điều đó đã không xảy ra sau đó.
Jonathan Leffler

POSIX findkhông cần xargsở nơi đầu tiên (và điều đó đã đúng 11 năm trước).
jlliagre

2

Với Bash (không phải POSIX), bạn có thể sử dụng thay thế quy trình để có được dòng hiện tại bên trong một biến. Điều này cho phép bạn sử dụng dấu ngoặc kép để thoát các ký tự đặc biệt:

while read line ; do cp "$line" ~/bar ; done < <(find . | grep foo)

2

Đối với tôi, tôi đã cố gắng làm một cái gì đó hơi khác một chút. Tôi muốn sao chép các tập tin .txt của mình vào thư mục tmp của mình. Tên tệp .txt chứa khoảng trắng và ký tự dấu nháy đơn. Điều này làm việc trên máy Mac của tôi.

$ find . -type f -name '*.txt' | sed 's/'"'"'/\'"'"'/g' | sed 's/.*/"&"/'  | xargs -I{} cp -v {} ./tmp/

1

Nếu các phiên bản find và xarg trên hệ thống của bạn không hỗ trợ -print0-0chuyển đổi (ví dụ: AIX find và xargs), bạn có thể sử dụng mã tìm kiếm khủng khiếp này:

 find . -name "*foo*" | sed -e "s/'/\\\'/g" -e 's/"/\\"/g' -e 's/ /\\ /g' | xargs cp /your/dest

Ở đây sed sẽ chăm sóc thoát khỏi không gian và báo giá cho xargs.

Đã thử nghiệm trên AIX 5.3


1

Tôi đã tạo một tập lệnh trình bao bọc di động nhỏ có tên "xargsL" xung quanh "xargs" để giải quyết hầu hết các vấn đề.

Trái với xargs, xargsL chấp nhận một tên đường dẫn trên mỗi dòng. Tên đường dẫn có thể chứa bất kỳ ký tự nào ngoại trừ (rõ ràng) byte mới hoặc byte NUL.

Không có trích dẫn nào được cho phép hoặc hỗ trợ trong danh sách tệp - tên tệp của bạn có thể chứa tất cả các loại khoảng trắng, dấu gạch chéo ngược, backticks, ký tự đại diện shell và tương tự - xargsL sẽ xử lý chúng dưới dạng ký tự bằng chữ, không gây hại.

Là một tính năng bổ sung, xargsL sẽ không chạy lệnh một lần nếu không có đầu vào!

Lưu ý sự khác biệt:

$ true | xargs echo no data
no data

$ true | xargsL echo no data # No output

Bất kỳ đối số nào được cung cấp cho xargsL sẽ được chuyển qua xargs.

Đây là tập lệnh shell POSIX "xargsL":

#! /bin/sh
# Line-based version of "xargs" (one pathname per line which may contain any
# amount of whitespace except for newlines) with the added bonus feature that
# it will not execute the command if the input file is empty.
#
# Version 2018.76.3
#
# Copyright (c) 2018 Guenther Brunthaler. All rights reserved.
#
# This script is free software.
# Distribution is permitted under the terms of the GPLv3.

set -e
trap 'test $? = 0 || echo "$0 failed!" >& 2' 0

if IFS= read -r first
then
        {
                printf '%s\n' "$first"
                cat
        } | sed 's/./\\&/g' | xargs ${1+"$@"}
fi

Đặt tập lệnh vào một số thư mục trong $ PATH của bạn và đừng quên

$ chmod +x xargsL

kịch bản ở đó để làm cho nó thực thi.


1

Phiên bản Perl của bill_starr sẽ không hoạt động tốt đối với các dòng mới được nhúng (chỉ đối phó với khoảng trắng). Đối với những người trên, ví dụ như Solaris nơi bạn không có các công cụ GNU, một phiên bản hoàn chỉnh hơn có thể là (sử dụng sed) ...

find -type f | sed 's/./\\&/g' | xargs grep string_to_find

điều chỉnh các đối số find và grep hoặc các lệnh khác khi bạn yêu cầu, nhưng sed sẽ sửa các dòng / không gian / tab được nhúng của bạn.


1

Tôi đã sử dụng câu trả lời của Bill Star được sửa đổi một chút trên Solaris:

find . -mtime +2 | perl -pe 's{^}{\"};s{$}{\"}' > ~/output.file

Điều này sẽ đặt dấu ngoặc kép trên mỗi dòng. Tôi đã không sử dụng tùy chọn '-l' mặc dù nó có thể sẽ giúp ích.

Danh sách tệp tôi đang đi mặc dù có thể có '-', nhưng không phải là dòng mới. Tôi đã không sử dụng tệp đầu ra với bất kỳ lệnh nào khác vì tôi muốn xem lại những gì đã tìm thấy trước khi tôi bắt đầu xóa chúng một cách ồ ạt qua xargs.


1

Tôi đã chơi với nó một chút, bắt đầu suy ngẫm sửa đổi xargs và nhận ra rằng đối với loại trường hợp sử dụng mà chúng ta đang nói ở đây, một cách thực hiện đơn giản trong Python là một ý tưởng tốt hơn.

Đối với một điều, có ~ 80 dòng mã cho toàn bộ điều đó có nghĩa là rất dễ để biết được chuyện gì đang xảy ra và nếu cần có hành vi khác, bạn có thể hack nó vào một tập lệnh mới trong thời gian ngắn hơn để có được một câu trả lời trên một nơi nào đó như Stack Overflow.

Xem https://github.com/johnallsup/jda-misc-scripts/blob/master/yargshttps://github.com/johnallsup/jda-misc-scripts/blob/master/zargs.py .

Với yargs như đã viết (và Python 3 đã cài đặt), bạn có thể nhập:

find .|grep "FooBar"|yargs -l 203 cp --after ~/foo/bar

để thực hiện sao chép 203 tệp cùng một lúc. (Tất nhiên, ở đây 203 chỉ là một trình giữ chỗ, và việc sử dụng một số lạ như 203 cho thấy rõ rằng số này không có ý nghĩa khác.)

Nếu bạn thực sự muốn một cái gì đó nhanh hơn và không cần Python, hãy lấy zarg và yarg làm nguyên mẫu và viết lại trong C ++ hoặc C.


0

Bạn có thể cần phải grep thư mục Foobar như:

find . -name "file.ext"| grep "FooBar" | xargs -i cp -p "{}" .

1
Trên mỗi trang người đàn ông, -ikhông được dùng nữa, và -Inên được sử dụng thay thế.
Acumenus

-1

Nếu bạn đang sử dụng Bash, bạn có thể chuyển đổi thiết bị xuất chuẩn thành một mảng các dòng bằng cách mapfile:

find . | grep "FooBar" | (mapfile -t; cp "${MAPFILE[@]}" ~/foobar)

Những lợi ích là:

  • Nó được tích hợp sẵn, vì vậy nó nhanh hơn.
  • Thực hiện lệnh với tất cả các tên tệp trong một lần, vì vậy nó sẽ nhanh hơn.
  • Bạn có thể nối các đối số khác vào tên tệp. Đối với cp, bạn cũng có thể:

    find . -name '*FooBar*' -exec cp -t ~/foobar -- {} +
    

    tuy nhiên, một số lệnh không có tính năng như vậy.

Những nhược điểm:

  • Có thể không mở rộng quy mô nếu có quá nhiều tên tệp. (Giới hạn? Tôi không biết, nhưng tôi đã thử nghiệm với tệp danh sách 10 MB bao gồm hơn 10000 tên tệp không có vấn đề gì, trong Debian)

Chà ... ai biết nếu Bash có sẵn trên OS X?

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.