Problem ze spacjami w nazwach plików

8

Chcę coś robić wielokrotnie na liście plików. Pliki w pytaniach mają spacje w nazwach:

david@david: ls -l
total 32
-rw-rw-r-- 1 david david 13 Mai  8 11:55 haha
-rw-rw-r-- 1 david david  0 Mai  8 11:55 haha~
-rw-rw-r-- 1 david david 13 Mai  8 11:55 haha (3rd copy)
-rw-rw-r-- 1 david david 13 Mai  8 11:55 haha (4th copy)
-rw-rw-r-- 1 david david 13 Mai  8 11:55 haha (5th copy)
-rw-rw-r-- 1 david david 13 Mai  8 11:55 haha (6th copy)
-rw-rw-r-- 1 david david 13 Mai  8 11:55 haha (7th copy)
-rw-rw-r-- 1 david david 13 Mai  8 11:55 haha (another copy)
-rw-rw-r-- 1 david david 13 Mai  8 11:55 haha (copy)

Teraz chcę statystyki każdego z tych plików:

david@david: echo '
for file in $(ls)
do
stat $file
done' | bash

(Używam echa i potoku do pisania poleceń wieloliniowych.)

Kiedy to robię, działa poprawnie na plikach, które nie mają spacji w nazwach. Ale inni ...

stat: cannot stat ‘(another’: No such file or directory
stat: cannot stat ‘copy)’: No such file or directory

Zmiana $(ls)na "$(ls)"lub $filedo "$file"nie działa. Co mogę zrobić?

Edytować:

echo '
for files in *
do
stat "$files"
done' | bash

Zrób sztuczkę! Ponieważ jestem nowy w bash, chcę, aby wszystko było tak proste, jak to możliwe - więc nic z próbą ucieczki ze spacji, użycia xargslub rozwiązania read -r, chociaż rozwiązują problem.

Jak niektórzy pytają: Tak, używanie tego zamiast stat *jest dziwne. Ale chciałem tylko znaleźć ogólny sposób zastosowania tego samego polecenia na paczce nazw plików w bash, używając pętli for. Więc statmoże oznaczać gzip, gpglub rm.

użytkownik258532
źródło
1
co jest nie tak stat *? (;-)
Rmano
Zobacz także mywiki.wooledge.org/ParsingLs
Rmano
Używam stat jako przykładu. :) Chcę zebrać nazwy plików za pomocą ls, a następnie użyć wyników ls w pętli bash.
user258532

Odpowiedzi:

12

Wielokrotny cytat z tego echo 'komplikuje sprawę.

Możesz po prostu użyć:

for f in *; do stat -- "$f"; done

Ale również

stat -- * 

... a jeśli chcesz zebrać pliki, a następnie zastosować polecenie (dlaczego?) możesz przejść (ale uważaj na plik zawierający nowe linie ... (1))

for f in *; do echo "$f"; done | xargs stat --

... a jeśli chcesz też ukrytych plików, po prostu użyj * .*jako wzorca, ale pamiętaj o tym .i ..będzie w zestawie .

Poza tym nie powinieneś analizować lsdanych wyjściowych .


(1) ale jeśli masz nazwy plików z nowymi liniami, to w pewnym sensie zasługujesz na to ... ;-)

Rmano
źródło
", a następnie zastosuj polecenie (dlaczego?)" -> stat służy tylko jako arbitralne polecenie, ponieważ próbuję zrobić pętle bash z nazwami plików. Może to być gpg, gzip lub cokolwiek innego.
user258532
@ user258532 niezależnie od polecenia, zawsze używaj for f in *; do command "$f"; done. Nigdy nie parsuj ls, z pewnością nigdy nie rób tego w for pętli i po co używać echo?
terdon
echo: Ponieważ lubię pisać polecenia w wielu wierszach ... :)
user258532
2
@ user258532 Huh? Dlaczego potrzebujesz echa? Wystarczy nacisnąć Enter i kontynuować na nowej linii. Jeśli kończy linię z otwartym cytat lub na jednym z do, |, &&etc, można kontynuować na nowej linii. Albo to, albo użyj heredoków. Bez powodu do używania echoi może również powodować problemy.
terdon
„Wystarczy nacisnąć Enter i kontynuować na nowej linii”. Auć. :-D Teraz moje IQ jest oficjalnie poniżej 0 - wiesz, jestem naprawdę nowy w bash i takie tam. Ale tak naprawdę zarabiam pieniądze na R (pakiecie statystycznym i języku skryptowym).
user258532
6

Na marginesie: możesz podzielić długie / skomplikowane polecenia na wiele wierszy, dodając spację, a następnie odwrotny ukośnik i uderzając Enterza każdym razem, gdy chcesz zacząć pisać do nowej linii, zamiast rozwiązywać wiele procesów za pomocą echo [...] | bash; należy również zawrzeć $filew cudzysłowie, aby zapobiec statłamaniu w przypadku nazw plików zawierających spacje:

for file in $(ls); \
do \
stat "$file"; \
done

Problem polega na tym, że $(ls)rozwija się do listy nazw plików zawierających spacje, i to samo stanie się z "$(ls)".

Nawet rozwiązując ten problem, ta metoda będzie nadal działać na nazwach plików zawierających ukośniki odwrotne i na nazwach plików zawierających znaki nowej linii (jak wskazał terdon).

Rozwiązaniem dla obu problemów byłoby potokowanie wyjścia finddo whileuruchomionej pętli, read -raby przy każdej iteracji read -rprzechowywał jeden wiersz findwyjścia w $file:

find . -maxdepth 1 -type f | while read -r file; do \
    stat "$file"; \
done
kos
źródło
1
i ukryte pliki? :)
AB
5
To nadal zawiedzie w nazwach plików zawierających nowe linie. Po prostu nie parsuj ls. Zawsze.
terdon
4
@ user258532 nie, nie ma. Poważnie, po prostu nie parsujls . Istnieją lepsze i bardziej niezawodne sposoby. Możesz także przeczytać to: Dlaczego * nie * parsować `ls`? po więcej szczegółów.
terdon
1
Problem nie polega na tym ls, że for- foriteruje (oddzielone IFS) słowa podane po insłowie kluczowym`
glenn jackman
1
@glennjackman no tak, to połączenie fori ls. for f in *na przykład byłoby dobrze.
terdon
3

Użyj starego dobrego find, działa z ukrytymi plikami, znakami nowej linii i spacjami.

find . -print0 | xargs -I {} -0 stat {}

lub jakikolwiek inny zamiast stat

find . -print0 | xargs -I {} -0 file {}
find . -print0 | xargs -I {} -0 cat {}
AB
źródło
1

Jako facet R już znalazłem obejście w R:

filenames <- dir(); # reads file names into an array.
                    # It works also recursively
                    # with dir(recursive=TRUE)
for (i in 1:length(filenames)) {
system(     # calls a system function
 paste(     # join stat and the file name
  "stat",
  filenames[i]
 )
)
}

Wiem, to szalone. Chciałbym, aby wynik lsbył łatwiejszy do przeanalizowania ... R może zajmować się spacjami, ponieważ dir () zwraca wartość znaku cytowanego. Cokolwiek między cudzysłowami to poprawna nazwa pliku ze spacjami.

użytkownik258532
źródło
3
Nie przejmuj się (ale +1 za wysiłek! :). Wystarczy użyć for f in *, naciśnij Enter i kontynuuj w nowej linii do:, naciśnij Enter ponownie stat "$f", Enter ponownie, doneEnter. To polecenie podzielone ładnie na 4 linie i nie złamie żadnej nazwy pliku.
terdon
1

Natrafiłem na inne przypadki problemów z białymi znakami w pętlach for, więc generalnie używam następującego (imo bardziej niezawodnego) polecenia. Ładnie pasuje również do rur.

$ ls | while read line; do stat "$line"; done;

Możesz połączyć to z greplub findzamiast tego użyć :

$ find ./ -maxdepth 2 | grep '^\./[/a-z]+$' | while read line; do stat "$line"; done;
Tyzoid
źródło
Twoja pierwsza odpowiedź kończy się niepowodzeniem w przypadku nazw plików zawierających odwrotny ukośnik lub spacje wiodące lub końcowe. Twoja druga odpowiedź nie powiedzie się całkowicie, chyba że dodasz -Eopcję grep, bez której nie zostanie rozpoznana +w wyrażeniu regularnym. Nawet wtedy jest to huśtawka i brak w tym pytaniu, ponieważ grep usuwasz nazwy plików zawierające spacje . Usuwa również nazwy plików zawierające cyfry (cyfry) i znaki interpunkcyjne (np. Nawiasy), tak jak w przykładach w pytaniu. I to nawet nie wspominając o nazwach plików, które zawierają -znak nowej linii lub zaczynają się od (myślnik).
Scott,
-1

Zamiast tego możesz zmienić nazwę swoich plików, zastępując spację inną postacią, taką jak podkreślenie, aby pozbyć się tego problemu:

Aby to zrobić, uruchom polecenie:

for file in * ; do mv "$f" "${f// /_}" ; done
Maythux
źródło
-2

Ta odpowiedź rozwiąże problem parsowania lsi zajmie się backspaceami i nowymi liniami

Spróbuj tego, rozwiąże Twój problem za pomocą wewnętrznego separatora pól IFS.

IFS="\n" for f in $(ls); do   stat "$f"; done

Ale możesz też łatwo go rozwiązać bez potrzeby analizowania danych wyjściowych za pomocą

for f in *; do   stat "$f"; done
Maythux
źródło
1
nie działa dla ukrytych plików.
AB
2
OP nie poprosił o ukryte pliki
Maythux
1
Nie rozumiem, dlaczego musisz to IFStutaj zmodyfikować : z pewnością cytowanie zmiennej powinno wystarczyć, aby zapobiec podziałowi słów?
steeldriver
do parsowania ls .
Maythux
Za co jest opinia !!!
Maythux