Mam skrypt powłoki bash, który przechodzi przez wszystkie katalogi podrzędne (ale nie pliki) określonego katalogu. Problem polega na tym, że niektóre nazwy katalogów zawierają spacje.
Oto zawartość mojego katalogu testowego:
$ls -F test
Baltimore/ Cherry Hill/ Edison/ New York City/ Philadelphia/ cities.txt
Oraz kod, który przechodzi przez katalogi:
for f in `find test/* -type d`; do
echo $f
done
Oto wynik:
test / Baltimore test / Cherry Wzgórze test / Edison test / nowy York Miasto test / Philadelphia
Cherry Hill i Nowy Jork są traktowane jako 2 lub 3 osobne wpisy.
Próbowałem zacytować nazwy plików, na przykład:
for f in `find test/* -type d | sed -e 's/^/\"/' | sed -e 's/$/\"/'`; do
echo $f
done
ale bez skutku.
Musi być na to prosty sposób.
Odpowiedzi poniżej są świetne. Ale żeby to jeszcze bardziej skomplikować - nie zawsze chcę używać katalogów wymienionych w moim katalogu testowym. Czasami chcę zamiast tego przekazać nazwy katalogów jako parametry wiersza polecenia.
Przyjąłem sugestię Charlesa dotyczącą ustawienia IFS i wymyśliłem, co następuje:
dirlist="${@}"
(
[[ -z "$dirlist" ]] && dirlist=`find test -mindepth 1 -type d` && IFS=$'\n'
for d in $dirlist; do
echo $d
done
)
i działa to dobrze, chyba że w argumentach wiersza poleceń są spacje (nawet jeśli te argumenty są cytowane). Na przykład wywołanie skryptu w ten sposób: test.sh "Cherry Hill" "New York City"
daje następujący wynik:
wiśnia Wzgórze Nowy York Miasto
źródło
list="$@"
całkowicie odrzuca listę pierwotnej wartości, zwijając ją do łańcucha. Proszę postępować zgodnie z praktykami zawartymi w mojej odpowiedzi dokładnie tak, jak podano - nigdzie nie zachęca się do takiego przydziału; jeśli chcesz przekazać listę argumentów wiersza poleceń do programu, powinieneś zebrać je w tablicy i bezpośrednio rozwinąć tę tablicę.Odpowiedzi:
Po pierwsze, nie rób tego w ten sposób. Najlepszym podejściem jest
find -exec
prawidłowe użycie :Innym bezpiecznym podejściem jest użycie listy zakończonej znakiem NUL, chociaż wymaga to wsparcia wyszukiwania
-print0
:Możesz również wypełnić tablicę z funkcji find i przekazać ją później:
Jeśli twoje znalezisko nie obsługuje
-print0
, twój wynik jest niebezpieczny - poniższe nie będą zachowywać się zgodnie z oczekiwaniami, jeśli istnieją pliki zawierające znaki nowej linii w nazwie (co, tak, jest legalne):Jeśli nie mamy zamiaru używać jednego z powyższych, trzecim podejściem (mniej wydajnym pod względem czasu i wykorzystania pamięci, ponieważ odczytuje całe wyjście podprocesu przed wykonaniem podziału na słowa) jest użycie
IFS
zmiennej, która nie nie zawierają znaku spacji. Wyłącz globbing (set -f
), aby zapobiec ciągi znaków zawierające takie jak glob[]
,*
lub?
przed rozwinięciem:Na koniec, w przypadku parametrów wiersza poleceń, powinieneś używać tablic, jeśli twoja powłoka je obsługuje (tj. Jest to ksh, bash lub zsh):
utrzyma separację. Zauważ, że cytowanie (i użycie
$@
zamiast$*
) jest ważne. Tablice można również wypełniać na inne sposoby, na przykład wyrażenia glob:źródło
Jednak nie działa, jeśli nazwa pliku zawiera znaki nowej linii. Powyższe jest jedynym rozwiązaniem, jakie znam, kiedy faktycznie chcesz mieć nazwę katalogu w zmiennej. Jeśli chcesz po prostu wykonać jakąś komendę, użyj xargs.
źródło
find . -type d | while read file; do ls "$file"; done
Oto proste rozwiązanie, które obsługuje tabulatory i / lub spacje w nazwie pliku. Jeśli masz do czynienia z innymi dziwnymi znakami w nazwie pliku, takimi jak znaki nowej linii, wybierz inną odpowiedź.
Katalog testów
Kod, aby przejść do katalogów
Jeśli nazwa pliku jest
"$f"
używana jako argument, należy ją wpisać w cudzysłów ( ). Bez cudzysłowów spacje działają jako separator argumentów, a wywoływanemu poleceniu jest podawanych wiele argumentów.A wynik:
źródło
alias duc='ls -d * | while read D; do du -sh "$D"; done;'
alias duc='du -sh *(/)'
-print0
iIFS='' read -r -d '' f
.Jest to niezwykle trudne w standardowym Uniksie, a większość rozwiązań zawiera znaki nowej linii lub inne znaki. Jeśli jednak używasz zestawu narzędzi GNU, możesz wykorzystać tę
find
opcję-print0
i użyćxargs
z odpowiednią opcją-0
(minus-zero). Istnieją dwa znaki, które nie mogą pojawić się w prostej nazwie pliku; to są slash i NUL '\ 0'. Oczywiście ukośnik pojawia się w nazwach ścieżek, więc rozwiązanie GNU polegające na użyciu NUL '\ 0' do oznaczenia końca nazwy jest genialne i niezawodne.źródło
Dlaczego po prostu nie położyć
przed dowództwem? Spowoduje to zmianę separatora pól z <Spacja> <Tab> <Nowa linia> na <Nowa linia>
źródło
źródło
-d $'\0'
jest dokładnie tym samym, co-d ''
- ponieważ bash używa ciągów zakończonych znakiem NUL, pierwszym znakiem pustego łańcucha jest NUL iz tego samego powodu wartości NUL w ogóle nie mogą być reprezentowane wewnątrz łańcuchów C.używam
Czy to nie wystarczy?
Pomysł zaczerpnięty z http://www.cyberciti.biz/tips/handling-filenames-with-spaces-in-bash.html
źródło
$(echo ...)
), nie obsługuje poprawnie nazw plików z wyrażeniami glob, nie obsługuje poprawnie nazw plików, które zawierają$'\b'
lub znaki $ '\ n', a ponadto konwertuje wiele ciągów białych znaków na pojedyncze białe znaki na strona wyjściowa z powodu nieprawidłowego cytowania.Nie przechowuj list jako ciągów; przechowuj je jako tablice, aby uniknąć całego zamieszania w separatorach. Oto przykładowy skrypt, który będzie działał na wszystkich podkatalogach testu lub na liście podanej w jego wierszu poleceń:
Teraz wypróbujmy to w katalogu testowym z wrzuconą krzywą lub dwiema:
źródło
"$@"
tablicę, dołączając do niego zset -- "$@" "$f"
.Możesz tymczasowo użyć IFS (wewnętrznego separatora pól), używając:
źródło
ps jeśli chodzi tylko o spację na wejściu, to niektóre podwójne cudzysłowy działały dla mnie gładko ...
źródło
Aby dodać do tego, co powiedział Jonathan : użyj
-print0
opcjifind
w połączeniu zxargs
następującymi:Spowoduje to wykonanie polecenia
command
z odpowiednimi argumentami; katalogi ze spacjami będą odpowiednio cytowane (tj. zostaną przekazane jako jeden argument).źródło
Powyższy kod konwertuje pliki .mov do .avi. Pliki .mov znajdują się w różnych folderach, a nazwy folderów mają spacje . Mój powyższy skrypt sam skonwertuje pliki .mov do pliku .avi w tym samym folderze. Nie wiem, czy to wam pomoże, ludzie.
Walizka:
Twoje zdrowie!
źródło
echo "$name" | ...
nie działa, jeśliname
jest-n
, a sposób, w jaki zachowuje się z nazwami z sekwencjami ucieczki odwrotnym ukośnikiem, zależy od twojej implementacji - POSIX sprawia, że zachowanieecho
w tym przypadku jest jawnie niezdefiniowane (podczas gdy rozszerzony XSI POSIX sprawia, że rozwinięcie sekwencji ucieczki odwrotnym ukośnikiem jest standardowe zachowanie , a systemy GNU - w tym bash - bezPOSIXLY_CORRECT=1
przerywania standardu POSIX poprzez implementację-e
(podczas gdy specyfikacja wymagaecho -e
drukowania-e
na wyjściu).printf '%s\n' "$name" | ...
jest bezpieczniejsze.Musiał też mieć do czynienia ze spacjami w nazwach ścieżek. W końcu użyłem rekurencji i
for item in /path/*
:źródło
function
słowa kluczowego - sprawia, że twój kod jest niekompatybilny z POSIX sh, ale nie ma innego użytecznego celu. Możesz po prostu zdefiniować funkcję za pomocąrecursedir() {
, dodając dwa pareny i usuwając słowo kluczowe function, a będzie to zgodne ze wszystkimi powłokami zgodnymi z POSIX.Przekonwertuj listę plików na tablicę Bash. Wykorzystuje podejście Matta McClure'a do zwracania tablicy z funkcji Bash: http://notes-matthewlmcclure.blogspot.com/2009/12/return-array-from-bash-function-v-2.html Wynikiem jest sposób aby przekonwertować dowolne wejście wieloliniowe na tablicę Bash.
To podejście wydaje się działać nawet wtedy, gdy obecne są złe znaki i jest ogólnym sposobem konwersji dowolnego wejścia na tablicę Bash. Wadą jest to, że jeśli dane wejściowe są długie, możesz przekroczyć limity rozmiaru wiersza poleceń Basha lub zużywać duże ilości pamięci.
Podejścia, w których pętla, która ostatecznie działa na liście, również ma tę listę, ma tę wadę, że odczyt stdin nie jest łatwy (na przykład proszenie użytkownika o wprowadzenie danych), a pętla jest nowym procesem, więc możesz się zastanawiać, dlaczego zmienne ustawione wewnątrz pętli nie są dostępne po zakończeniu pętli.
Nie podoba mi się też ustawianie IFS, może to zepsuć inny kod.
źródło
IFS='' read
, w tym samym wierszu, ustawienie IFS jest obecne tylko dla polecenia odczytu i nie powoduje zmiany znaczenia. Nie ma powodu, by nie lubić ustawiania IFS w ten sposób.Cóż, widzę zbyt wiele skomplikowanych odpowiedzi. Nie chcę przekazywać danych wyjściowych narzędzia find ani pisać pętli, ponieważ find ma do tego opcję "exec".
Mój problem polegał na tym, że chciałem przenieść wszystkie pliki z rozszerzeniem dbf do bieżącego folderu, a niektóre z nich zawierały spacje.
Tak sobie z tym poradziłem:
Wydaje mi się to bardzo proste
źródło
właśnie się dowiedziałem, że istnieją pewne podobieństwa między moim pytaniem a twoim. Na razie, jeśli chcesz przekazywać argumenty do poleceń
wydrukować je w kolejności
zauważ, że $ @ jest otoczony podwójnymi cudzysłowami, kilka uwag tutaj
źródło
Potrzebowałem tej samej koncepcji, aby skompresować sekwencyjnie kilka katalogów lub plików z określonego folderu. Rozwiązałem użycie awk do przeanalizowania listy z ls i uniknięcia problemu spacji w nazwie.
co myślisz?
źródło
źródło
U mnie to działa i jest prawie „czyste”:
źródło
Właśnie miałem prosty problem z wariantem ... Konwertuj pliki o typie .flv do .mp3 (ziewanie).
rekurencyjnie znajdź wszystkie pliki flash użytkownika Macintosha i zamień je na audio (kopiuj, bez transkodowania) ... to tak jak powyżej, zauważ, że odczyt zamiast tylko „dla pliku w
” zostanie usunięty.
źródło
read
Poin
jeszcze jeden wyraz w liście jesteś iteracji skończona. To, co opublikowałeś, jest nieco zepsutą wersją tego, co miał pytający, co nie działa. Być może zamierzałeś zamieścić coś innego, ale prawdopodobnie i tak jest to objęte innymi odpowiedziami.