Jak mogę używać xargs do kopiowania plików ze spacjami i cudzysłowami w nazwach?

232

Próbuję skopiować kilka plików poniżej katalogu, a niektóre z nich mają spacje i cudzysłowy w swoich nazwach. Podczas próby ciągnięcia razem findi za greppomocą xargstego pojawia się następujący błąd:

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

Wszelkie sugestie dotyczące bardziej niezawodnego korzystania z xargs?

To jest na Mac OS X 10.5.3 (Leopard) z BSD xargs.

Drew Stephens
źródło
2
Komunikat o błędzie GNU xargs w tym przypadku z nazwą pliku zawierającą pojedynczy cytat jest raczej bardziej pomocny: „xargs: niedopasowany pojedynczy cytat; domyślnie cytaty są specjalne dla xargs, chyba że użyjesz opcji -0”.
Steve Jessop
3
GNU xargs ma również --delimiteropcję ( -d). Wypróbuj \njako separator, Zapobiega to xargsrozdzielaniu wierszy ze spacjami na kilka słów / argumentów.
MattBianco

Odpowiedzi:

199

Możesz połączyć to wszystko w jedno findpolecenie:

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

Będzie to obsługiwać nazwy plików i katalogów ze spacjami w nich. Możesz użyć, -nameaby uzyskać wyniki z rozróżnianiem wielkości liter.

Uwaga: --przekazana flaga cpuniemożliwia przetwarzanie plików zaczynających się od -jako opcji.

do widzenia
źródło
70
Ludzie używają xargs, ponieważ zazwyczaj szybsze jest wywoływanie pliku wykonywalnego 5 razy z 200 argumentami za każdym razem, niż wywoływanie go 1000 razy z jednym argumentem za każdym razem.
tzot
12
Odpowiedź Chrisa Jester-Younga powinna być „dobrą odpowiedzią” tam… BTW to rozwiązanie nie działa, jeśli nazwa pliku zaczyna się od „-”. Przynajmniej potrzebuje „-” po cp.
Keltia
11
Przykład prędkości - ponad 829 plików metoda „find -exec” zajęła 26 sekund, a narzędzie metody „find -print0 | xargs --null” 0,7 sekundy. Znacząca różnica.
Peter Porter
7
@tzot Późny komentarz, ale tak czy inaczej, xargsnie jest wymagany do rozwiązania opisywanego problemu, findobsługuje go już -exec +interpunkcja.
jlliagre
3
nie odpowiada na pytanie, jak radzić sobie z przestrzenią
Ben Glasser,
117

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

Nie wiem, czy grepobsługuje --null, czy też xargsobsługuje -0, na Leopardzie, ale na GNU wszystko jest dobre.

Chris Jester-Young
źródło
1
Leopard obsługuje „-Z” (jest to GNU grep) i oczywiście find (1) oraz xargs (1) obsługują „-0”.
Keltia
1
W systemie OS X 10.9 grep -{z|Z}oznacza „zachowuj się jak zgrep” (dekompresja), a nie zamierzone „drukowanie bajtu zerowego po każdej nazwie pliku”. Użyj, grep --nullaby osiągnąć to drugie.
bassim
4
Co jest nie tak z find . -name 'FooBar' -print0 | xargs -0 ...?
Quentin Pradet
1
@QuentinPradet Oczywiście dla ustalonego ciągu, takiego jak „FooBar”, -namelub -pathdziała dobrze. OP określił użycie grep, prawdopodobnie dlatego, że chce filtrować listę przy użyciu wyrażeń regularnych.
Chris Jester-Young
1
@ Hi-Anioł to dokładnie dlaczego używam xargs -0 w połączeniu z find -print0 . Drugi drukuje nazwy plików za pomocą terminatora NUL, a pierwszy odbiera pliki w ten sposób. Czemu? Nazwy plików w systemie Unix mogą zawierać znaki nowego wiersza. Ale nie mogą zawierać znaków NUL.
Chris Jester-Young
92

Najłatwiejszym sposobem na zrobienie tego, co chce oryginalny plakat, jest zmiana separatora z dowolnej białej spacji na znak końca linii, taki jak ten:

find whatever ... | xargs -d "\n" cp -t /var/tmp
użytkownik87601
źródło
4
Ta odpowiedź jest prosta, skuteczna i od razu do rzeczy: domyślny zestaw ograniczników dla xargs jest zbyt szeroki i musi zostać zawężony do tego, co OP chce zrobić. Znam to z pierwszej ręki, ponieważ napotkałem dziś dokładnie ten sam problem, robiąc coś podobnego, z wyjątkiem cygwina. Gdybym przeczytał pomoc dotyczącą polecenia xargs, mógłbym uniknąć kilku problemów, ale twoje rozwiązanie to rozwiązało. Dzięki ! (Tak, OP działał na MacOS przy użyciu xargs BSD, którego nie używam, ale mam nadzieję, że parametr „-d” xargs istnieje we wszystkich wersjach).
Etienne Delavennat
7
Dobra odpowiedź, ale nie działa na komputerze Mac. Zamiast tego możemy wprowadzić szukanie w sed -e 's_\(.*\)_"\1"_g'celu wymuszenia cudzysłowu wokół nazwy pliku
ishahak
10
To powinna być zaakceptowana odpowiedź. Pytanie dotyczyło użycia xargs.
Mohammad Alhashash
2
Dostajęxargs: illegal option -- d
nehem
1
Warto zauważyć, że nazwy plików mogą zawierać znak nowej linii w wielu systemach * nix. Prawdopodobnie nigdy nie spotkasz się z tym na wolności, ale jeśli uruchamiasz polecenia powłoki przy niezaufanym wejściu, może to stanowić problem.
Soren Bjornstad,
71

Jest to bardziej wydajne, ponieważ nie uruchamia wielokrotnie „cp”:

find -name '*FooBar*' -print0 | xargs -0 cp -t ~/foo/bar
Tometzky
źródło
1
To mi nie zadziałało. Próbował cp ~ / foo / bar w cokolwiek znajdziesz, ale nie odwrotnie
Shervin Asgari
13
Flaga -t na cp jest rozszerzeniem GNU, AFAIK, i nie jest dostępna w OS X. Ale gdyby tak było, działałby tak, jak pokazano w tej odpowiedzi.
metamatt
2
Używam Linuksa. Dzięki za przełącznik „-t”. Tego mi brakowało :-)
Vahid Pazirandeh 21.04.17
59

Natrafiłem na ten sam problem. Oto jak to rozwiązałem:

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

Kiedyś sedzastępowałem każdy wiersz wejścia tą samą linią, ale otoczony podwójnymi cudzysłowami. Ze strony podręcznika sed... Znak ampersand (` `& '') występujący w zastępstwie jest zastępowany ciągiem pasującym do RE ... ” - w tym przypadku .*cała linia.

To rozwiązuje xargs: unterminated quotebłąd.

oyouareatubeo
źródło
3
Używam Windowsa i używam gnuwin32, więc musiałem go użyć sed s/.*/\"&\"/, żeby działał.
Pat
Tak, ale przypuszczalnie nie poradziłoby to z nazwami plików z "in - chyba że sed również cytuje cytaty?
artfulrobot
Używanie sedjest genialne i na razie poprawne rozwiązanie bez przepisywania problemu!
entonio
53

Ta metoda działa w systemie Mac OS X 10.7.5 (Lion):

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

Przetestowałem również dokładną składnię, którą opublikowałeś. Działa to również dobrze w wersji 10.7.5.

the_minted
źródło
4
Działa to, ale -Isugeruje -L 1(tak mówi instrukcja), co oznacza, że ​​polecenie cp jest uruchamiane raz na plik = v wolne.
artfulrobot
xargs -J% cp% <katalog docelowy> Prawdopodobnie jest bardziej wydajny w OSX.
Walker D
3
Przepraszam, ale to jest ZŁE. Najpierw powoduje dokładnie błąd, którego TO chciał uniknąć. Musisz używać find ... -print0i xargs -0pracować wokół „xargs” domyślnie są specjalne ”. Po drugie, zwykle '{}'nie używaj {}poleceń przekazywanych do xargs, aby chronić przed spacjami i znakami specjalnymi.
Andreas Spindler,
3
Niestety, Andreas Spindler, nie znam się na Xargs i znalazłem tę linię po kilku eksperymentach. Wydaje się, że działa dla większości ludzi, którzy skomentowali go i ocenili. Czy mógłbyś podać nieco więcej szczegółów na temat tego, jaki rodzaj błędu powoduje? Czy zechcesz opublikować dokładne dane, które Twoim zdaniem byłyby bardziej poprawne? Dziękuję Ci.
the_minted
12

Po prostu nie używaj xargs. Jest to fajny program, ale nie pasuje do findnietrudnych przypadków.

Oto przenośne (POSIX) rozwiązanie, to znaczy taki, który nie wymaga find, xargslub cpGNU konkretnych rozszerzeń:

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

Zwróć uwagę na zakończenie +zamiast bardziej zwykłego ;.

To rozwiązanie:

  • poprawnie obsługuje pliki i katalogi z osadzonymi spacjami, znakami nowej linii lub dowolnymi egzotycznymi znakami.

  • działa na każdym systemie Unix i Linux, nawet tym, który nie udostępnia zestawu narzędzi GNU.

  • nie używa xargsładnego i użytecznego programu, ale wymaga zbyt wiele poprawek i niestandardowych funkcji, aby poprawnie obsługiwać finddane wyjściowe.

  • jest również bardziej wydajny ( szybszy odczyt ) niż przyjęte i większość, jeśli nie wszystkie inne odpowiedzi.

Zauważ też, że pomimo tego, co podano w niektórych innych odpowiedziach lub komentarzach, cytowanie {}jest bezużyteczne (chyba że używasz egzotycznej fishpowłoki).

jlliagre
źródło
1
@PeterMortensen Prawdopodobnie przeoczysz końcowy plus. findmoże robić to, co xargsrobi bez żadnych kosztów ogólnych.
jlliagre
8

Sprawdź użycie opcji wiersza polecenia --null dla xargs z opcją -print0 w find.

Shannon Nelson
źródło
8

Dla tych, którzy polegają na poleceniach innych niż find, np . ls:

find . | grep "FooBar" | tr \\n \\0 | xargs -0 -I{} cp "{}" ~/foo/bar
Aleksandr Guidrevitch
źródło
1
Działa, ale powoli, ponieważ -Isugeruje-L 1
artfulrobot
6
find | perl -lne 'print quotemeta' | xargs ls -d

Uważam, że będzie to działało niezawodnie dla każdego znaku, z wyjątkiem przesyłu wiersza (i podejrzewam, że jeśli masz przesuwanie wiersza w nazwach plików, to masz gorsze problemy niż to). Nie wymaga GNU findutils, tylko Perl, więc powinien działać prawie wszędzie.

mavit
źródło
Czy możliwe jest wstawienie wiersza w nazwie pliku? Nigdy nie słyszałem o tym.
mtk
2
Rzeczywiście jest. Spróbuj, np.mkdir test && cd test && perl -e 'open $fh, ">", "this-file-contains-a-\n-here"' && ls | od -tx1
mavit
1
|perl -lne 'print quotemeta'jest dokładnie tym, czego szukałem. Inne posty tutaj mi nie pomogły, ponieważ zamiast findmusiałem grep -rlznacznie zmniejszyć liczbę plików PHP do tylko zainfekowanych złośliwym oprogramowaniem.
Marcos
Perl i quotemeta są o wiele bardziej ogólne niż print0 / -0 - dzięki za ogólnym rozwiązanie szybkiego odbierania plików ze spacjami
bmike
5

Przekonałem się, że następująca składnia działa dla mnie dobrze.

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

W tym przykładzie szukam największych 200 plików powyżej 1 000 000 bajtów w systemie plików zamontowanym w „/ usr / pcapps”.

W linijce Perla między „find” a „xargs” znaki ucieczki / cytowania są puste, więc „xargs” przekazuje dowolną nazwę pliku z osadzonymi spacjami do „ls” jako pojedynczy argument.

Peter Mortensen
źródło
3

Wyzwanie ramowe - pytasz, jak używać xargs. Odpowiedź brzmi: nie używasz xargs, ponieważ go nie potrzebujesz.

Komentarzuser80168 opisuje sposób to zrobić bezpośrednio z CP, bez wywoływania cp dla każdego pliku:

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

Działa to, ponieważ:

  • cp -tflaga pozwala podać katalog docelowy w pobliżu początku cp, a nie pod koniec. Od man cp:
   -t, --target-directory=DIRECTORY
         copy all SOURCE arguments into DIRECTORY
  • --Flaga mówi cpinterpretować wszystko po jako nazwy pliku, a nie flagi, więc pliki rozpoczynające się -lub --nie mylić cp; nadal jest to potrzebne, ponieważ znaki -/ --są interpretowane przez cp, podczas gdy inne znaki specjalne są interpretowane przez powłokę.

  • find -exec command {} +Wariant zasadniczo działa tak samo jak xargs. Od 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.

Używając tego bezpośrednio w funkcji wyszukiwania, pozwala to uniknąć potrzeby wywoływania potoku lub powłoki, dzięki czemu nie trzeba się martwić o paskudne znaki w nazwach plików.

gerrit
źródło
Niesamowite znalezisko, nie miałem pojęcia !!! "-exec utility [argument ...] {} + Taki sam jak -exec, z wyjątkiem tego, że` `{} '' jest zastępowane tyloma nazwami ścieżek, jak to możliwe dla każdego wywołania narzędzia. To zachowanie jest podobne do zachowania xargs (1 ). ” we wdrażaniu BSD.
conny
2

Należy pamiętać, że większość opcji omówionych w innych odpowiedziach nie jest standardem na platformach, które nie używają narzędzi GNU (na przykład Solaris, AIX, HP-UX). Zobacz specyfikację POSIX dla „standardowych” zachowań xargs.

Uważam również, że zachowanie xargs, w którym uruchamia polecenie co najmniej raz, nawet bez danych wejściowych, jest uciążliwe.

Napisałem własną prywatną wersję xargs (xargl), aby poradzić sobie z problemami spacji w nazwach (oddzielne są tylko nowe wiersze - chociaż kombinacja „znajdź ... -print0” i „xargs -0” jest całkiem fajna, biorąc pod uwagę, że nazwy plików nie mogą zawierają znaki ASCII NUL „\ 0.” Mój xargl nie jest tak kompletny, jak by go warto było opublikować - zwłaszcza, że ​​GNU ma udogodnienia, które są co najmniej tak dobre.

Jonathan Leffler
źródło
2
GitHub albo tak się nie stało
Corey Goldberg
@CoreyGoldberg: Chyba tak się nie stało.
Jonathan Leffler
POSIX przede wszystkim findnie potrzebuje xargs(a było to prawdą już 11 lat temu).
jlliagre
2

Dzięki Bash (nie POSIX) możesz użyć podstawienia procesu, aby uzyskać bieżący wiersz w zmiennej. Umożliwia to stosowanie cudzysłowów do znaków specjalnych:

while read line ; do cp "$line" ~/bar ; done < <(find . | grep foo)
StackedCrooked
źródło
2

Dla mnie próbowałem zrobić coś nieco innego. Chciałem skopiować moje pliki .txt do mojego folderu tmp. Nazwy plików .txt zawierają spacje i znaki apostrofów. To działało na moim Macu.

$ find . -type f -name '*.txt' | sed 's/'"'"'/\'"'"'/g' | sed 's/.*/"&"/'  | xargs -I{} cp -v {} ./tmp/
Moises
źródło
1

Jeśli find a xarg wersje na komputerze nie obsługuje -print0i -0przełączniki (na przykład AIX znaleźć i xargs) Można użyć tego kodu strasznie patrząc:

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

Tutaj sed zajmie się ucieczką w miejsca i cytatami dla xargs.

Testowane w systemie AIX 5.3

Jan Ptáčník
źródło
1

Stworzyłem mały przenośny skrypt otoki o nazwie „xargsL” wokół „xargs”, który rozwiązuje większość problemów.

W przeciwieństwie do xargs, xargsL akceptuje jedną ścieżkę w linii. Ścieżki mogą zawierać dowolny znak oprócz (oczywiście) nowej linii lub bajtów NUL.

Cytowanie na liście plików nie jest dozwolone ani obsługiwane - nazwy plików mogą zawierać wszelkiego rodzaju białe znaki, ukośniki odwrotne, znaki wsteczne, znaki wieloznaczne powłoki i tym podobne - xargsL przetworzy je jako znaki dosłowne, bez szkody.

Jako dodatkowa funkcja bonusowa, xargsL nie uruchomi polecenia raz, jeśli nie ma danych wejściowych!

Zwróć uwagę na różnicę:

$ true | xargs echo no data
no data

$ true | xargsL echo no data # No output

Wszelkie argumenty podane xargsL zostaną przekazane do xargs.

Oto skrypt powłoki 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

Umieść skrypt w jakimś katalogu w $ PATH i nie zapomnij

$ chmod +x xargsL

skrypt tam, aby był wykonywalny.

Guenther Brunthaler
źródło
1

Wersja Perla bill_starr nie działa dobrze dla osadzonych znaków nowej linii (tylko kopiuje ze spacjami). Dla tych na np. Solaris, gdzie nie masz narzędzi GNU, bardziej kompletna wersja może być (używając sed) ...

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

dostosuj argumenty find i grep lub inne polecenia według potrzeb, ale sed naprawi osadzone znaki nowej linii / spacje / tabulatory.

Peter Mortensen
źródło
1

W Solarisie użyłem nieco zmodyfikowanej odpowiedzi Billa Star :

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

Spowoduje to umieszczenie cudzysłowów wokół każdej linii. Nie użyłem opcji „-l”, chociaż prawdopodobnie to pomogłoby.

Lista plików, którą wybrałem, może mieć „-”, ale nie nowe wiersze. Nie użyłem pliku wyjściowego z żadnymi innymi poleceniami, ponieważ chcę sprawdzić, co zostało znalezione, zanim zacznę masowo je usuwać za pomocą xargs.

Carl Yamamoto-Furst
źródło
1

Trochę się z tym bawiłem, zacząłem zastanawiać się nad modyfikacją xargs i zdałem sobie sprawę, że dla tego rodzaju zastosowania, o którym tu mówimy, lepszym pomysłem jest prosta ponowna implementacja w Pythonie.

Po pierwsze, posiadanie ~ 80 linii kodu dla całej rzeczy oznacza, że ​​łatwo jest dowiedzieć się, co się dzieje, a jeśli wymagane jest inne zachowanie, możesz po prostu włamać go do nowego skryptu w krótszym czasie niż potrzeba, aby uzyskać odpowiedź na coś takiego jak przepełnienie stosu.

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

Mając napisane yargs (i zainstalowany Python 3), możesz pisać:

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

wykonać kopiowanie 203 plików jednocześnie. (Tutaj 203 jest oczywiście symbolem zastępczym, a użycie dziwnej liczby, takiej jak 203, wyraźnie pokazuje, że liczba ta nie ma innego znaczenia).

Jeśli naprawdę chcesz czegoś szybciej i bez potrzeby używania Pythona, weź Zargs i Yargs jako prototypy i przepisz w C ++ lub C.

John Allsup
źródło
0

Może być konieczne grepowanie katalogu Foobar, takiego jak:

find . -name "file.ext"| grep "FooBar" | xargs -i cp -p "{}" .
Fred
źródło
1
Na stronie podręcznika -ijest przestarzały i -Inależy go użyć zamiast tego.
Acumenus
-1

Jeśli używasz Bash, możesz przekonwertować standardowe wyjście na tablicę wierszy poprzez mapfile:

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

Korzyści to:

  • Jest wbudowany, więc jest szybszy.
  • Wykonaj polecenie ze wszystkimi nazwami plików jednocześnie, dzięki czemu jest szybszy.
  • Możesz dołączyć inne argumenty do nazw plików. Dla cp, można również:

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

    jednak niektóre polecenia nie mają takiej funkcji.

Wady:

  • Może nie skaluje się dobrze, jeśli jest zbyt wiele nazw plików. (Limit? Nie wiem, ale testowałem z plikiem listy 10 MB, który zawiera ponad 10000 nazw plików bez problemu, w Debianie)

Cóż ... kto wie, czy Bash jest dostępny na OS X?

Xiè Jìléi
źródło