Bagaimana saya bisa menggunakan xargs untuk menyalin file yang memiliki spasi dan tanda kutip dalam namanya?


232

Saya mencoba untuk menyalin banyak file di bawah direktori dan sejumlah file memiliki spasi dan tanda kutip tunggal dalam namanya. Ketika saya mencoba untuk string bersama finddan grepdengan xargs, saya mendapatkan error berikut:

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

Adakah saran untuk penggunaan xarg yang lebih kuat?

Ini ada di Mac OS X 10.5.3 (Leopard) dengan BSD xargs.


2
Pesan kesalahan GNU xargs untuk ini dengan nama file yang berisi kutipan tunggal agak lebih membantu: "xargs: kutipan tunggal tak tertandingi; dengan kutipan default adalah khusus untuk xargs kecuali Anda menggunakan opsi -0".
Steve Jessop

3
GNU xargs juga memiliki --delimiteropsi ( -d). Cobalah dengan \nsebagai pembatas, ini mencegah xargsmemisahkan garis dengan spasi menjadi beberapa kata / argumen.
MattBianco

Jawaban:


199

Anda dapat menggabungkan semua itu menjadi satu findperintah:

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

Ini akan menangani nama file dan direktori dengan spasi di dalamnya. Anda dapat menggunakan -nameuntuk mendapatkan hasil case-sensitive.

Catatan: --Bendera berlalu untuk cpmencegahnya memproses file yang dimulai dengan -sebagai opsi.


70
Orang-orang menggunakan xargs karena biasanya lebih cepat untuk memanggil executable 5 kali dengan 200 argumen setiap kali daripada menyebutnya 1000 kali dengan satu argumen setiap kali.
tzot

12
Jawaban dari Chris Jester-Young seharusnya menjadi "jawaban yang baik" di sana ... BTW solusi ini tidak berfungsi jika nama file dimulai dengan "-". Setidaknya, perlu "-" setelah cp.
Keltia

11
Contoh kecepatan - lebih dari 829 file, metode "find -exec" membutuhkan waktu 26 detik sedangkan alat metode "find -print0 | xargs --null" 0,7 detik. Perbedaan yang signifikan.
Peter Porter

7
@tzot Sebuah komentar terlambat tetapi bagaimanapun, xargstidak diperlukan untuk mengatasi masalah yang Anda gambarkan, findsudah mendukungnya dengan -exec +tanda baca.
jlliagre

3
tidak menjawab pertanyaan tentang bagaimana menangani ruang
Ben Glasser

117

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

Saya tidak tahu apakah grepmendukung --null, atau xargsmendukung -0, di Leopard, tetapi di GNU itu semua baik.


1
Leopard memang mendukung "-Z" (ini GNU grep) dan tentu saja menemukan (1) dan xargs (1) mendukung "-0".
Keltia

1
Pada OS X 10.9 grep -{z|Z}berarti "berperilaku sebagai zgrep" (dekompresi) dan tidak dimaksudkan "mencetak byte nol setelah setiap nama file". Gunakan grep --nulluntuk mencapai yang terakhir.
bassim

4
Ada apa dengan ini find . -name 'FooBar' -print0 | xargs -0 ...?
Quentin Pradet

1
@ QuentinPradet Jelas, untuk string yang tetap seperti "FooBar", -nameatau -pathberfungsi dengan baik. OP telah menentukan penggunaan grep, mungkin karena mereka ingin memfilter daftar menggunakan ekspresi reguler.
Chris Jester-Young

1
@ Hi-Angel Itu ini persis mengapa saya menggunakan xargs -0 dalam hubungannya dengan find -print0 . Yang terakhir mencetak nama file dengan terminator NUL dan mantan menerima file seperti itu. Mengapa? Nama file di Unix dapat berisi karakter baris baru. Tetapi mereka tidak dapat berisi karakter NUL.
Chris Jester-Young

92

Cara termudah untuk melakukan apa yang diinginkan oleh poster asli adalah mengubah pembatas dari spasi putih menjadi hanya karakter end-of-line seperti ini:

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

4
Browser ini sederhana, efektif, dan langsung ke titik: pembatas default yang ditetapkan untuk xargs terlalu luas dan perlu dipersempit untuk apa yang ingin dilakukan OP. Saya tahu ini secara langsung karena saya mengalami masalah yang sama persis hari ini melakukan hal yang serupa, kecuali di cygwin. Seandainya saya membaca bantuan untuk perintah xargs, saya mungkin telah menghindari beberapa sakit kepala, tetapi solusi Anda memperbaikinya untuk saya. Terima kasih! (Ya, OP menggunakan MacOS menggunakan BSD xargs, yang tidak saya gunakan, tapi saya berharap bahwa parameter "-d" xargs ada di semua versi).
Etienne Delavennat

7
Jawaban yang bagus tetapi tidak bekerja pada Mac. Sebagai gantinya, kita dapat sed -e 's_\(.*\)_"\1"_g'
mem

10
Ini harus menjadi jawaban yang diterima. Pertanyaannya adalah tentang penggunaan xargs.
Mohammad Alhashash

2
Saya mendapatkanxargs: illegal option -- d
nehem

1
Patut ditunjukkan bahwa nama file dapat berisi karakter baris baru pada banyak sistem * nix. Anda tidak mungkin pernah mengalami ini di alam liar, tetapi jika Anda menjalankan perintah shell pada input yang tidak dipercaya ini bisa menjadi masalah.
Soren Bjornstad

71

Ini lebih efisien karena tidak menjalankan "cp" beberapa kali:

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

1
Ini tidak berhasil untuk saya. Itu mencoba untuk cp ~ / foo / bar menjadi apa pun yang Anda temukan, tetapi tidak sebaliknya
Shervin Asgari

13
Bendera -t ke cp adalah ekstensi GNU, AFAIK, dan tidak tersedia di OS X. Tetapi jika ya, itu akan berfungsi seperti yang ditunjukkan dalam jawaban ini.
metamatt

2
Saya menggunakan Linux. Terima kasih untuk sakelar '-t'. Itulah yang saya lewatkan :-)
Vahid Pazirandeh

59

Saya mengalami masalah yang sama. Inilah cara saya menyelesaikannya:

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

Saya biasa sedmengganti setiap baris input dengan baris yang sama, tetapi dikelilingi oleh tanda kutip ganda. Dari sedhalaman manual, " ... Sebuah ampersand (` `& '') yang muncul pada penggantian diganti dengan string yang cocok dengan RE ... " - dalam hal ini,, .*seluruh baris.

Ini menyelesaikan xargs: unterminated quotekesalahan.


3
Saya menggunakan windows dan menggunakan gnuwin32, jadi saya harus menggunakannya sed s/.*/\"&\"/untuk membuatnya bekerja.
Pat

Ya tapi mungkin ini tidak akan menangani nama file dengan "in - kecuali sed juga mengutip harga?
artfulrobot

Menggunakan sedjenius dan untuk saat ini solusi yang tepat tanpa menulis ulang masalahnya!
entonio

53

Metode ini berfungsi pada Mac OS X v10.7.5 (Lion):

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

Saya juga menguji sintaks yang tepat yang Anda posting. Itu juga bekerja dengan baik pada 10.7.5.


4
Ini berfungsi, tetapi -Itersirat -L 1(demikian kata manual), yang berarti perintah cp sedang dijalankan sekali per file = v lambat.
artfulrobot

xargs -J% cp% <tujuan dir> Mungkin lebih efisien pada OSX.
Walker D

3
Maaf, ini SALAH. Pertama, ia menghasilkan kesalahan yang ingin dihindari oleh TO. Anda harus menggunakan find ... -print0dan xargs -0bekerja di sekitar xargs "dengan kutipan default adalah khusus". Kedua, umumnya menggunakan '{}'tidak {}dalam perintah yang dikirimkan ke xargs, untuk melindungi terhadap spasi dan karakter khusus.
Andreas Spindler

3
Maaf Andreas Spindler, saya tidak begitu terbiasa dengan xargs dan menemukan baris ini setelah beberapa percobaan. Tampaknya bekerja untuk sebagian besar orang yang telah mengomentari dan meningkatkannya. Maukah Anda sedikit lebih detail tentang jenis kesalahan apa yang dihasilkannya? Juga, maukah Anda memposting input yang tepat yang menurut Anda lebih benar? Terima kasih.
the_minted

12

Hanya saja, jangan gunakan xargs. Ini adalah program yang rapi tetapi tidak berjalan dengan baik findketika dihadapkan dengan kasus-kasus yang tidak sepele.

Berikut ini adalah solusi portabel (POSIX), yaitu solusi yang tidak memerlukan find, xargsatau cpekstensi spesifik GNU:

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

Catat akhirnya +bukan yang lebih biasa ;.

Solusi ini:

  • menangani file dan direktori dengan benar dengan ruang yang disematkan, baris baru, atau karakter eksotis apa pun.

  • bekerja pada sistem Unix dan Linux apa pun, bahkan yang tidak menyediakan toolkit GNU.

  • tidak menggunakan program xargsmana yang bagus dan bermanfaat, tetapi membutuhkan terlalu banyak penyesuaian dan fitur non-standar untuk menangani findkeluaran dengan benar .

  • juga lebih efisien (baca lebih cepat ) daripada yang diterima dan sebagian besar jika tidak semua jawaban lainnya.

Perhatikan juga bahwa meskipun apa yang dinyatakan dalam balasan atau komentar lain mengutip {}tidak berguna (kecuali jika Anda menggunakan fishshell eksotis ).



1
@PeterMortensen Anda mungkin mengabaikan akhiran plus. finddapat melakukan apa xargstanpa overhead.
jlliagre

8

Lihatlah ke dalam menggunakan opsi --null commandline untuk xargs dengan opsi -print0 di find.


8

Bagi mereka yang bergantung pada perintah, selain menemukan, misalnya ls:

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

1
Bekerja tetapi lambat karena -Imenyiratkan-L 1
artfulrobot

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

Saya percaya bahwa ini akan bekerja dengan andal untuk karakter apa pun kecuali umpan baris (dan saya menduga bahwa jika Anda mendapat umpan baris di nama file Anda, maka Anda memiliki masalah yang lebih buruk dari ini). Itu tidak memerlukan GNU findutils, hanya Perl, jadi itu harus bekerja cukup banyak di mana saja.


Apakah mungkin untuk mendapatkan umpan baris dalam nama file? Tidak pernah mendengar hal tersebut.
mtk

2
Memang itu. Coba, misalnya,mkdir test && cd test && perl -e 'open $fh, ">", "this-file-contains-a-\n-here"' && ls | od -tx1
mavit

1
|perl -lne 'print quotemeta'persis apa yang saya cari. Posting lain di sini tidak membantu saya karena findsaya harus menggunakan grep -rluntuk mengurangi jumlah file PHP menjadi hanya yang terinfeksi malware.
Marcos

perl dan quotemeta jauh lebih umum daripada print0 / -0 - terima kasih atas solusi umum untuk pipelining file dengan spasi
bmike

5

Saya telah menemukan bahwa sintaks berikut bekerja dengan baik untuk saya.

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

Dalam contoh ini, saya mencari 200 file terbesar lebih dari 1.000.000 byte di sistem file yang dipasang di "/ usr / pcapps".

Baris-liner Perl antara "find" dan "xargs" lolos / mengutip setiap blank sehingga "xargs" meneruskan setiap nama file dengan blanko yang tertanam ke "ls" sebagai argumen tunggal.


3

Bingkai tantangan - Anda bertanya bagaimana menggunakan xargs. Jawabannya adalah: Anda tidak menggunakan xargs, karena Anda tidak membutuhkannya.

The komentar olehuser80168 menggambarkan cara untuk melakukan hal ini langsung dengan cp, tanpa menyebut cp untuk setiap file:

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

Ini berfungsi karena:

  • yang cp -tbendera memungkinkan untuk memberikan target direktori dekat awal cp, daripada mendekati akhir. Dari man cp:
   -t, --target-directory=DIRECTORY
         copy all SOURCE arguments into DIRECTORY
  • The --bendera mengatakan cpuntuk menafsirkan semuanya setelah sebagai nama file, bukan bendera, sehingga file yang dimulai dengan -atau --tidak membingungkan cp; Anda masih memerlukan ini karena -/ --karakter ditafsirkan oleh cp, sedangkan karakter khusus lainnya ditafsirkan oleh shell.

  • The find -exec command {} +varian dasarnya melakukan hal yang sama seperti xargs. Dari 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.

Dengan menggunakan ini dalam find secara langsung, ini menghindari kebutuhan pipa atau doa shell, sehingga Anda tidak perlu khawatir tentang karakter jahat dalam nama file.


Temuan yang luar biasa, saya tidak tahu !!! "-exec utility [argumen ...] {} + Sama dengan -exec, kecuali bahwa` `{} '' diganti dengan sebanyak mungkin nama path untuk setiap pemanggilan utilitas. Perilaku ini mirip dengan xargs (1 ). " dalam implementasi BSD.
conny

2

Ketahuilah bahwa sebagian besar opsi yang dibahas dalam jawaban lain tidak standar pada platform yang tidak menggunakan utilitas GNU (Solaris, AIX, HP-UX, misalnya). Lihat spesifikasi POSIX untuk perilaku xarg 'standar'.

Saya juga menemukan perilaku xargs di mana ia menjalankan perintah setidaknya sekali, bahkan tanpa input, menjadi gangguan.

Saya menulis versi pribadi saya sendiri xargs (xargl) untuk menangani masalah spasi dalam nama (hanya baris baru terpisah - meskipun kombinasi 'find ... -print0' dan 'xargs -0' cukup rapi mengingat nama file tidak dapat mengandung karakter ASCII NUL '\ 0'. xargl saya tidak selengkap yang dibutuhkan untuk diterbitkan - terutama karena GNU memiliki fasilitas yang setidaknya sama baiknya.


2
GitHub atau itu tidak terjadi
Corey Goldberg

@CoreyGoldberg: Saya kira itu tidak terjadi saat itu.
Jonathan Leffler

POSIX findtidak perlu xargsdi tempat pertama (dan itu sudah benar 11 tahun yang lalu).
jlliagre

2

Dengan Bash (bukan POSIX) Anda bisa menggunakan proses substitusi untuk mendapatkan baris saat ini di dalam variabel. Ini memungkinkan Anda menggunakan kutipan untuk keluar dari karakter khusus:

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

2

Bagi saya, saya mencoba melakukan sesuatu yang sedikit berbeda. Saya ingin menyalin file .txt saya ke folder tmp saya. Nama file .txt berisi spasi dan karakter tanda kutip. Ini berfungsi pada Mac saya.

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

1

Jika versi find dan xarg pada sistem Anda tidak mendukung -print0dan -0beralih (misalnya find dan xarg AIX), Anda dapat menggunakan kode yang sangat kelihatan ini:

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

Di sini sed akan mengurus pelarian ruang dan mengutip untuk xargs.

Diuji pada AIX 5.3


1

Saya membuat skrip pembungkus portabel kecil yang disebut "xargsL" di sekitar "xargs" yang membahas sebagian besar masalah.

Berlawanan dengan xargs, xargsL menerima satu pathname per baris. Nama path dapat berisi karakter apa pun kecuali (jelas) baris baru atau byte NUL.

Kutipan tidak diperbolehkan atau didukung dalam daftar file - nama file Anda dapat berisi semua jenis spasi putih, backslash, backticks, karakter wildcard shell dan sejenisnya - xargsL akan memprosesnya sebagai karakter literal, tidak ada salahnya dilakukan.

Sebagai fitur bonus tambahan, xargsL tidak akan menjalankan perintah sekali jika tidak ada input!

Perhatikan perbedaannya:

$ true | xargs echo no data
no data

$ true | xargsL echo no data # No output

Setiap argumen yang diberikan kepada xargsL akan diteruskan ke xargs.

Berikut ini skrip 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

Masukkan skrip ke beberapa direktori di $ PATH Anda dan jangan lupa

$ chmod +x xargsL

skrip di sana untuk membuatnya dapat dieksekusi.


1

Versi Perl bill_starr tidak akan bekerja dengan baik untuk baris baru yang disematkan (hanya berupaya dengan spasi). Bagi mereka yang menggunakan Solaris misalnya di mana Anda tidak memiliki alat GNU, versi yang lebih lengkap mungkin (menggunakan sed) ...

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

sesuaikan find dan grep argumen atau perintah lain seperti yang Anda butuhkan, tetapi sed akan memperbaiki baris / spasi / tab baru Anda.


1

Saya menggunakan jawaban Bill Star yang sedikit dimodifikasi pada Solaris:

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

Ini akan menempatkan tanda kutip di sekitar setiap baris. Saya tidak menggunakan opsi '-l' meskipun mungkin akan membantu.

Daftar file yang saya tuju mungkin memiliki '-', tetapi bukan baris baru. Saya belum menggunakan file output dengan perintah lain karena saya ingin meninjau apa yang ditemukan sebelum saya baru saja mulai menghapusnya secara besar-besaran melalui xargs.


1

Saya bermain dengan ini sedikit, mulai merenungkan memodifikasi xargs, dan menyadari bahwa untuk jenis use case yang sedang kita bicarakan di sini, implementasi ulang sederhana dengan Python adalah ide yang lebih baik.

Untuk satu hal, memiliki ~ 80 baris kode untuk keseluruhan berarti mudah untuk mengetahui apa yang sedang terjadi, dan jika diperlukan perilaku yang berbeda, Anda dapat meretasnya menjadi skrip baru dalam waktu yang lebih singkat daripada waktu yang dibutuhkan untuk mendapatkan balasan di suatu tempat seperti Stack Overflow.

Lihat https://github.com/johnallsup/jda-misc-scripts/blob/master/yargs dan https://github.com/johnallsup/jda-misc-scripts/blob/master/zargs.py .

Dengan yargs seperti ditulis (dan Python 3 terpasang) Anda dapat mengetik:

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

untuk melakukan menyalin 203 file sekaligus. (Di sini 203 hanya pengganti, tentu saja, dan menggunakan nomor aneh seperti 203 memperjelas bahwa nomor ini tidak memiliki makna lain.)

Jika Anda benar-benar menginginkan sesuatu yang lebih cepat dan tanpa perlu Python, gunakan zargs dan yargs sebagai prototipe dan tulis ulang dalam C ++ atau C.


0

Anda mungkin perlu grep direktori Foobar seperti:

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

1
Per halaman manual, -isudah usang, dan -Iharus digunakan sebagai gantinya.
Acumenus

-1

Jika Anda menggunakan Bash, Anda dapat mengonversi stdout ke array baris dengan mapfile:

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

Manfaatnya adalah:

  • Ini built-in, jadi lebih cepat.
  • Jalankan perintah dengan semua nama file dalam satu waktu, jadi ini lebih cepat.
  • Anda dapat menambahkan argumen lain ke nama file. Untuk cp, Anda juga bisa:

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

    Namun, beberapa perintah tidak memiliki fitur tersebut.

Kerugiannya:

  • Mungkin tidak skala dengan baik jika ada terlalu banyak nama file. (Batasnya? Saya tidak tahu, tetapi saya telah menguji dengan 10 MB file daftar yang mencakup 10.000 nama file tanpa masalah, di bawah Debian)

Nah ... siapa yang tahu jika Bash tersedia di OS X?

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.