Jak korzystać z funkcji znajdź, gdy nazwa pliku zawiera spacje?

17

Chcę przesyłać nazwy plików do innych programów, ale wszystkie dławią się, gdy zawierają spacje.

Powiedzmy, że mam plik o nazwie.

foo bar

Jak mogę uzyskać findpoprawną nazwę?

Oczywiście chcę:

foo\ bar

lub:

"foo bar"

EDYCJA : Nie chcę przechodzić xargs, chcę uzyskać poprawnie sformatowany ciąg znaków, findaby móc przesyłać ciąg nazw plików bezpośrednio do innego programu.

pluskwa
źródło
5
do czego to robisz? czy znasz -execflagę z find? możesz potencjalnie złagodzić ten błąd i sprawić, że twoje polecenie będzie bardziej wydajne, wykonując je -execzamiast potokowania go do innych poleceń. Tylko moje 0,02 $
h3rrmiller
6
@bug: dobrze findformatuje nazwy plików; są zapisywane jedna nazwa w wierszu. (Oczywiście jest to niejednoznaczne, jeśli nazwa pliku zawiera znak nowego wiersza.) Problemem jest więc „zadławienie” końca odbierającego, gdy otrzymuje spację, co oznacza, że ​​musisz powiedzieć nam, co to jest koniec odbierający, jeśli chcesz sensowną odpowiedź .
rici
2
To, co nazywacie „właściwie sformatowanym”, tak naprawdę „ucieka do konsumpcji przez powłokę”. Większość programów narzędziowych, które potrafią odczytać kilka nazw plików, dławi się nazwą ucieczki powłoki, ale w rzeczywistości miałoby sens (powiedzmy) findzaoferowanie opcji wyświetlania nazw plików w formacie odpowiednim dla powłoki. Zasadniczo jednak rozszerzenie -print0GNU finddziała również w wielu innych scenariuszach i powinieneś nauczyć się go używać w każdym przypadku.
tripleee
2
@bug: Nawiasem mówiąc, ls $(command...)nie przesyła listy stdin. Umieszcza dane wyjściowe $(command...)bezpośrednio w wierszu polecenia. W takim przypadku jest to powłoka, która czyta z c, i użyje bieżącej wartości, $IFSaby zdecydować, jak wyrazić wyjście. Ogólnie lepiej jest używać xargs. Nie zauważysz uderzenia wydajności.
rici
2
find -printf '"%p"\n'doda podwójne cudzysłowy wokół każdej znalezionej nazwy, ale nie będzie poprawnie cytować żadnych podwójnych cudzysłowów w nazwie pliku. Jeśli w nazwach plików nie ma osadzonych podwójnych cudzysłowów, możesz zignorować problem: lub przepuścić sed 's/"/&&/g;s/^""/"/;s/""$/"/'. Jeśli twoje nazwy plików są obsługiwane przez powłokę, prawdopodobnie powinieneś użyć pojedynczych cudzysłowów zamiast podwójnych cudzysłowów (inaczej sweet$HOMEstanie się czymś podobnym sheet/home/you). I to wciąż nie jest bardzo odporne na nazwy plików z nowymi liniami. Jak chcesz sobie z tym poradzić?
tripleee

Odpowiedzi:

18

POSIXLY:

find . -type f -exec sh -c '
  for f do
    : command "$f"
  done
' sh {} +

Z findpodporami -print0i xargspodporami -0:

find . -type f -print0 | xargs -0 <command>

-0 opcja mówi xargsowi, aby użyła znaku NUL ASCII zamiast spacji do zakończenia (oddzielenia) nazw plików.

Przykład:

find . -maxdepth 1 -type f -print0 | xargs -0 ls -l
Cuonglm
źródło
Nie działa Kiedy biegnę ls $(find . -maxdepth 1 -type f -print0 | xargs -0), dostaję ls: cannot access ./foo: No such file or directory ls: cannot access bar: No such file or directory
błąd
1
Czy próbowałeś tego, jak napisał Gnouc? Jeśli nalegasz, aby zrobić to po swojemu, spróbuj $(..)"$(..)"
ująć w
3
@bug: twoje polecenie jest złe. Spróbuj dokładnie napisać stronę findi xargs.
cuonglm
Rozumiem, potem znowu chcę sformatować ciąg, który mógłbym bezpośrednio potokować.
błąd
1
@bug: Wystarczy użyć xargs -0 <twój program>
cuonglm
10

Używanie -print0jest jedną z opcji, ale nie wszystkie programy obsługują używanie strumieni danych rozdzielanych nullbyte, więc będziesz musiał używać xargstej -0opcji do niektórych rzeczy, jak zauważyła odpowiedź Gnouc.

Alternatywą byłoby użyć find„s -execlub -execdiropcji. Pierwsza z poniższych opcji będzie podawać nazwy plików somecommandpojedynczo, a druga rozwinie się do listy plików:

find . -type f -exec somecommand '{}' \;
find . -type f -exec somecommand '{}' +

Może się okazać, że lepiej jest używać globowania w wielu przypadkach. Jeśli masz nowoczesną powłokę (bash 4+, zsh, ksh), możesz uzyskać rekurencyjne globowanie za pomocą globstar( **). W bash musisz to ustawić:

shopt -s globstar
somecommand ./**/*.txt ## feeds all *.txt files to somecommand, recursively

Mam wiersz shopt -s globstar extglobw moim .bashrc, więc zawsze jest to dla mnie włączone (podobnie jak rozszerzone globusy, które są również przydatne).

Jeśli nie chcesz rekurencyjności, po prostu użyj ./*.txtzamiast tego, aby użyć każdego * .txt w katalogu roboczym. findma kilka bardzo przydatnych funkcji szczegółowego wyszukiwania i jest obowiązkowy dla dziesiątek tysięcy plików (w tym momencie natrafisz na maksymalną liczbę argumentów powłoki), ale w codziennym użyciu często jest to niepotrzebne.

zła
źródło
Hej @evilsoup, co {} robi w tym skrypcie?
Ayusman
3

Osobiście użyłbym -execakcji find, aby rozwiązać ten problem. Lub, jeśli to konieczne xargs, co pozwala na równoległe wykonywanie.

Istnieje jednak sposób na uzyskanie findlisty nazw plików do odczytu. Nic więc dziwnego, że używa -execi bash, w szczególności rozszerzenie do printfpolecenia:

find ... -exec bash -c 'printf "%q " "$@"' printf {} ';'

Jednak mimo tego, że wydrukuje on poprawnie słowa, które nie mają znaku powłoki, nie będzie można ich używać $(...), ponieważ $(...)nie interpretuje cudzysłowów ani znaków ucieczki. (Resut z $(...)jest podzielony na słowa i rozwinięcie nazwy ścieżki, chyba że jest otoczony cudzysłowami.) Więc następujące czynności nie będą robić, co chcesz:

ls $(find ... -exec bash -c 'printf "%q " "$@"' printf {} +)

Co musisz zrobić:

eval "ls $(find ... -exec bash -c 'printf "%q " "$@"' printf {} +)"

(Zauważ, że nie podjąłem żadnej prawdziwej próby przetestowania powyższej potworności.)

Ale równie dobrze możesz zrobić:

find ... -exec ls {} +
rici
źródło
Nie sądzę, aby lsscenariusz odpowiednio wychwycił przypadek użycia PO, ale jest to tylko spekulacja, ponieważ nie pokazano nam, co on tak naprawdę chce osiągnąć. To rozwiązanie działa naprawdę bardzo ładnie; Dostaję oczekiwany (niejasno) wynik dla wszystkich śmiesznych nazw plików, których próbowałem, w tymtouch "$(tr a-z '\001-\026' <<<'the quick brown fox jumped over the lazy dogs')"
tripleee
@triplee: Nie mam pojęcia, co OP chce zrobić. Jedyną prawdziwą zaletą konstruowania cytowanego ciągu do przekazania evaljest to, że nie musisz go evaljeszcze przekazywać ; możesz zapisać go w parametrze i użyć później, być może kilka razy z różnymi poleceniami. Jednak PO nie daje żadnych wskazówek, że jest to sprawa zastosowanie (a gdyby tak było, to może być lepiej umieścić nazwy plików do tablicy, choć to trudne, zbyt.)
Rici
0
find ./  | grep " "

dostanie ci pliki i katalogi zawierające spacje

find ./ -type f  | grep " " 

dostanie ci pliki zawiera spacje

find ./ -type d | grep " "

dostanie ci katalogi zawierające spacje

Kannan Kumarasamy
źródło
-2
    find . -type f -name \*\  | sed -e 's/ /<thisisspace>/g'
użytkownik 283965
źródło
To interesująca odpowiedź, ale nie jest odpowiedzią na to pytanie.
Scott