zapętlanie przez `ls` powoduje skrypt powłoki bash

93

Czy ktoś ma skrypt powłoki szablonów do robienia czegoś z lslistą nazw katalogów i do przechodzenia między nimi i robienia czegoś?

Planuję zrobić, ls -1d */aby uzyskać listę nazw katalogów.

Daniel A. White
źródło

Odpowiedzi:

109

Edytowano, aby nie używać ls tam, gdzie zrobiłby glob, jak sugerowali @ shawn-j-goff i inni.

Wystarczy użyć for..do..donepętli:

for f in *; do
  echo "File -> $f"
done

Można wymienić *z *.txtlub dowolny inny glob, która zwraca listę (plików, katalogów lub cokolwiek na ten temat), polecenie, które generuje listę, na przykład $(cat filelist.txt), czy rzeczywiście należy wymienić go na liście.

W dopętli po prostu odwołujesz się do zmiennej pętli z prefiksem znaku dolara (tak $fw powyższym przykładzie). Możesz echoto zrobić lub zrobić cokolwiek innego, co chcesz.

Na przykład, aby zmienić nazwę wszystkich .xmlplików w bieżącym katalogu na .txt:

for x in *.xml; do 
  t=$(echo $x | sed 's/\.xml$/.txt/'); 
  mv $x $t && echo "moved $x -> $t"
done

Lub jeszcze lepiej, jeśli używasz Bash, możesz użyć rozszerzeń parametrów Bash zamiast odradzania podpowłoki:

for x in *.xml; do 
  t=${x%.xml}.txt
  mv $x $t && echo "moved $x -> $t"
done
CoverosGene
źródło
22
Co jeśli nazwa pliku zawiera spację?
Daniel A. White
4
Niestety, jak powiedział Daniel, kod w tej odpowiedzi ulegnie uszkodzeniu, jeśli którykolwiek z plików lub folderów zawiera spację lub znak nowej linii w nazwie. Pokazuje bardzo częste niewłaściwe użycie forpętli i typową pułapkę podczas próby przetworzenia wyniku ls. @ DanielA.White, to może rozważyć unaccepting tę odpowiedź, jeśli nie był pomocny (lub potencjalnie mylące), ponieważ jak powiedziałeś, jesteś działając w katalogach. Odpowiedź Shawna J. Goffa powinna zapewnić bardziej niezawodne i działające rozwiązanie twojego problemu.
slhck
4
-∞ Nie powinieneś analizować lsdanych wyjściowych , nie powinieneś czytać danych wyjściowych w forpętli , powinieneś użyć $()zamiast `` i Użyj więcej cytatów ™ . Jestem pewien, że @CoverosGene miał dobre intencje, ale to po prostu okropne.
l0b0
1
Lepszą alternatywą, jeśli naprawdę chcesz skorzystać, lsjest ls -1 | while read line; do stuff; done. Przynajmniej ten nie złamie się dla białych znaków.
Emmanuel Joubaud,
1
z mv -vtobą nie potrzebujeszecho "moved $x -> $t"
DmitrySandalov
68

Korzystanie z danych wyjściowych w lscelu uzyskania nazw plików jest złym pomysłem . Może to prowadzić do nieprawidłowego działania, a nawet niebezpiecznych skryptów. To dlatego, że nazwa pliku może zawierać dowolny znak oprócz /i nullcharakter, a lsnie korzysta z żadnego z tych znaków jako ograniczniki, więc jeśli nazwa pliku zawiera spację lub znak nowej linii, to będzie otrzymać nieoczekiwane rezultaty.

Istnieją dwa bardzo dobre sposoby iteracji plików. Tutaj zwykłem echodemonstrować robienie czegoś z nazwą pliku; możesz jednak użyć wszystkiego.

Pierwszym z nich jest użycie natywnych funkcji globowania powłoki.

for dir in */; do
  echo "$dir"
done

Powłoka rozwija się */w osobne argumenty forodczytywane przez pętlę; nawet jeśli w nazwie pliku znajduje się spacja, znak nowej linii lub jakikolwiek inny dziwny znak, forkażda pełna nazwa będzie postrzegana jako jednostka atomowa; w żaden sposób nie analizuje listy.

Jeśli chcesz przechodzić rekurencyjnie do podkatalogów, nie będzie to możliwe, chyba że twoja powłoka ma jakieś rozszerzone funkcje globowania (takie jak bashs globstar. w różnych systemach, następną opcją jest użycie find.

find . -type d -exec echo '{}' \;

Tutaj findpolecenie wywoła echoi przekaże mu argument nazwy pliku. Robi to raz dla każdego znalezionego pliku. Podobnie jak w poprzednim przykładzie, nie ma parsowania listy nazw plików; zamiast tego nazwa pliku jest wysyłana całkowicie jako argument.

Składnia -execargumentu wygląda trochę zabawnie. findpobiera pierwszy argument za -execi traktuje go jako program do uruchomienia, a każdy kolejny argument przyjmuje jako argument do przekazania do tego programu. Są dwa specjalne argumenty, które -execnależy zobaczyć. Pierwszy to {}; ten argument zostaje zastąpiony nazwą pliku, którą findgenerują poprzednie części . Drugi to ;, który informuje find, że jest to koniec listy argumentów przekazywanych do programu; findpotrzebuje tego, ponieważ możesz kontynuować z większą liczbą argumentów, które są przeznaczone findi nie są przeznaczone dla wykonywanego programu. Powodem \jest to, że powłoka również traktuje;szczególnie - reprezentuje koniec polecenia, więc musimy uciec od niego, aby powłoka podała go jako argument findzamiast konsumować go dla siebie; innym sposobem, aby powłoka nie traktowała go specjalnie, jest umieszczenie go w cudzysłowach: ';'działa tak dobrze, jak \;w tym celu.

Shawn J. Goff
źródło
2
+1 jest to zdecydowanie droga, gdy musisz wygenerować listę plików i użyć jej w poleceniu. find -exec jest ograniczony przez możliwość uruchamiania tylko pojedynczych poleceń. Dzięki pętli możesz przepłynąć do treści twojego serca.
MaQleod,
+1. Chciałbym, żeby więcej osób skorzystało find. Istnieje magia, którą można zrobić, a nawet postrzegane ograniczenia -execmożna obejść. -print0Opcją jest także cenne dla użycia z xargs.
ghoti
Opcja pętli nie wyświetla ukrytych katalogów. Opcja znajdowania nie pokaże dowiązań symbolicznych.
jinawee
15

W przypadku plików ze spacjami musisz podać zmienną:

 for i in $(ls); do echo "$i"; done; 

lub możesz zmienić zmienną środowiskową separatora pól wejściowych (IFS):

 IFS=$'\n';for file in $(ls); do echo $i; done

Wreszcie, w zależności od tego, co robisz, możesz nawet nie potrzebować ls:

 for i in *; do echo "$i"; done;
Jan
źródło
miłe użycie podpowłoki w pierwszym przykładzie
Jeremy L
Dlaczego IFS potrzebuje $ po zadaniu i przed nową postacią?
Andy Ibanez
3
Nazwy plików mogą także zawierać znaki nowej linii. Włamanie \njest niewystarczające. Nigdy nie jest dobrym pomysłem zalecanie analizy wyniku ls.
ghoti
4

Aby dodać do odpowiedzi CoverosGene, oto sposób na podanie tylko nazw katalogów:

for f in */; do
  echo "Directory -> $f"
done
n3o
źródło
1

Dlaczego nie ustawić IFS na nowy wiersz, a następnie przechwycić dane wyjściowe lsw tablicy? Ustawienie IFS na nowy wiersz powinno rozwiązać problemy ze śmiesznymi znakami w nazwach plików; używanie lsmoże być przyjemne, ponieważ ma wbudowaną funkcję sortowania.

(Podczas testowania miałem problem z ustawieniem IFS na, \nale ustawienie na backspace nowej linii działa, jak sugerowano gdzie indziej tutaj):

Np. (Zakładając, że lsprzekazano pożądany wzorzec wyszukiwania $1):

SAVEIFS=$IFS
IFS=$(echo -en "\n\b")

FILES=($(/bin/ls "$1"))

for AFILE in ${FILES[@]}
do
    ... do something with a file ...
done

IFS=$SAVEIFS

Jest to szczególnie przydatne w systemie OS X, np. Aby przechwycić listę plików posortowanych według daty utworzenia (od najstarszej do najnowszej), lspolecenie to ls -t -U -r.

jetset
źródło
2
Nazwy plików mogą również zawierać znaki nowej linii i często tak się dzieje, gdy użytkownicy mogą nazywać własne pliki. Włamanie \njest niewystarczające. Jedyne prawidłowe rozwiązania używają pętli for z rozszerzaniem nazw ścieżek lub find.
ghoti
Jedynym niezawodnym sposobem przesłania listy nazw plików jest oddzielenie ich znakiem NUL, ponieważ jest to jedyny zdecydowanie nie zawarty w ścieżce pliku.
glglgl
-3

Tak to robię, ale są prawdopodobnie bardziej wydajne sposoby.

ls > filelist.txt

while read filename; do echo filename: "$filename"; done < filelist.txt
DRZEWO
źródło
6
Trzymaj się rur zamiast pliku:>ls | while read i; do echo filename: $i; done
Jeremy L
Chłodny. Powinienem powiedzieć, że można także użyć $ EDITOR filelist.txt pomiędzy tymi dwoma poleceniami. Wiele rzeczy można zrobić w edytorze, który jest łatwiejszy niż w wierszu poleceń. Nie dotyczy to jednak pytania.
DRZEWO
Twoje rozwiązanie w ogóle nie rozwiązuje problemu z nazwami plików zawierającymi znaki nowej linii i inne wymyślne rzeczy.
glglgl