Jak mogę uniknąć białych spacji na liście pętli bash?
Mam skrypt powłoki bash, który zapętla 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 przecina katalogi:
for f in `find test/* -type d`; do
echo $f
done
Oto wyjście:
test/Baltimore test/Cherry Hill test/Edison test/New York City test/Philadelphia
Cherry Hill i Nowy Jork są traktowane jako 2 lub 3 oddzielne wpisy.
Próbowałem cytować nazwy plików, jak więc:
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 aby to bardziej skomplikowane - nie zawsze chcę korzystać z katalogów wymienionych w moim katalogu testowym. Czasami chcę zamiast tego przekazać nazwy katalogów jako parametry wiersza poleceń.
Skorzystałem z sugestii Charlesa, aby ustawić 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 to działa dobrze, chyba że są spacje w argumenty linii poleceń (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ące wyjście:
Cherry Hill New York City
20 answers
Po pierwsze, nie rób tego w ten sposób. Najlepszym podejściem jest odpowiednie użycie find -exec
:
# this is safe
find test -type d -exec echo '{}' +
Innym bezpiecznym podejściem jest użycie listy zakończonej znakiem NUL, choć wymaga to Twojego wsparcia find -print0
:
# this is safe
while IFS= read -r -d '' n; do
printf '%q\n' "$n"
done < <(find test -mindepth 1 -type d -print0)
Możesz również wypełnić tablicę z find i przekazać ją później:
# this is safe
declare -a myarray
while IFS= read -r -d '' n; do
myarray+=( "$n" )
done < <(find test -mindepth 1 -type d -print0)
printf '%q\n' "${myarray[@]}" # printf is an example; use it however you want
Jeśli Twoje znalezisko nie obsługuje -print0
, Twój wynik jest niebezpieczny - poniżej nie zachowa się tak, jak chcesz, jeśli istnieją pliki zawierające nowe linie w ich nazwach (co, tak, jest prawne):
# this is unsafe
while IFS= read -r n; do
printf '%q\n' "$n"
done < <(find test -mindepth 1 -type d)
Jeśli nie chcesz użyć jednego z powyższych, trzecim podejściem (mniej wydajnym zarówno pod względem czasu, jak i wykorzystania pamięci, ponieważ odczytuje całe wyjście podprocesu przed wykonaniem podziału słów) jest użycie zmiennej IFS
, która nie zawiera znaku spacji. Wyłącz globbing (set -f
), aby zapobiec ciągom zawierającym znaki glob, takie jak []
, *
lub ?
z rozwinięcia:
# this is unsafe (but less unsafe than it would be without the following precautions)
(
IFS=$'\n' # split only on newlines
set -f # disable globbing
for n in $(find test -mindepth 1 -type d); do
printf '%q\n' "$n"
done
)
Wreszcie, dla przypadku parametru wiersza poleceń, należy używaj tablic, jeśli twoja powłoka je obsługuje (tzn. jest to ksh, bash lub zsh):
# this is safe
for d in "$@"; do
printf '%s\n' "$d"
done
Utrzyma separację. Zauważ, że cytowanie (i użycie $@
zamiast $*
) jest ważne. Tablice mogą być wypełniane również na inne sposoby, np. wyrażenia glob:
# this is safe
entries=( test/* )
for d in "${entries[@]}"; do
printf '%s\n' "$d"
done
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-06-27 16:29:27
find . -type d | while read file; do echo $file; done
Nie działa jednak, jeśli nazwa pliku zawiera nowe linie. Powyższe jest jedynym rozwiązaniem, jakie znam, gdy rzeczywiście chcesz mieć nazwę katalogu w zmiennej. Jeśli chcesz wykonać jakieś polecenie, użyj xargs.
find . -type d -print0 | xargs -0 echo 'The directory is: '
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-11-19 05:50:24
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 testowy
ls -F test
Baltimore/ Cherry Hill/ Edison/ New York City/ Philadelphia/ cities.txt
Kod do katalogów
find test -type d | while read f ; do
echo "$f"
done
Nazwa pliku musi być cytowana ("$f"
), jeśli jest używana jako argument. Bez cudzysłowów spacje działają jako separator argumentów, a wywołane polecenie otrzymuje wiele argumentów.
I wyjście:
test/Baltimore
test/Cherry Hill
test/Edison
test/New York City
test/Philadelphia
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-07-23 11:36:29
Jest to niezwykle trudne w standardowym Uniksie, a większość rozwiązań nie zawiera znaków nowej linii lub innych znaków. Jednakże, jeśli używasz zestawu narzędzi GNU, możesz wykorzystać opcję find
-print0
i użyć {[2] } z odpowiednią opcją -0
(minus-zero). Istnieją dwa znaki, które nie mogą pojawić się w prostej nazwie pliku; są to ukośniki i NUL '\0'. Oczywiście slash pojawia się w nazwach ścieżek, więc rozwiązanie GNU polegające na użyciu NUL '\0' do oznaczenia końca nazwy jest pomysłowe i / align = "left" /
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-11-19 05:45:43
Why not just put
IFS='\n'
Przed komendą for? Powoduje to zmianę separatora pól z naWarning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-02-26 11:07:26
Używam
SAVEIFS=$IFS
IFS=$(echo -en "\n\b")
for f in $( find "$1" -type d ! -path "$1" )
do
echo $f
done
IFS=$SAVEIFS
Czy to nie wystarczy?Pomysł wzięty z http://www.cyberciti.biz/tips/handling-filenames-with-spaces-in-bash.html
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-06-10 14:26:06
Nie przechowuje list jako łańcuchów; przechowuje je jako tablice, aby uniknąć pomyłek z ogranicznikami. Oto przykładowy skrypt, który będzie działał we wszystkich podkatalogach test lub na liście dostarczonej w wierszu poleceń:
#!/bin/bash
if [ $# -eq 0 ]; then
# if no args supplies, build a list of subdirs of test/
dirlist=() # start with empty list
for f in test/*; do # for each item in test/ ...
if [ -d "$f" ]; then # if it's a subdir...
dirlist=("${dirlist[@]}" "$f") # add it to the list
fi
done
else
# if args were supplied, copy the list of args into dirlist
dirlist=("$@")
fi
# now loop through dirlist, operating on each one
for dir in "${dirlist[@]}"; do
printf "Directory: %s\n" "$dir"
done
Teraz wypróbujmy to na katalogu testowym z krzywą lub dwoma wrzuconymi:
$ ls -F test
Baltimore/
Cherry Hill/
Edison/
New York City/
Philadelphia/
this is a dirname with quotes, lfs, escapes: "\''?'?\e\n\d/
this is a file, not a directory
$ ./test.sh
Directory: test/Baltimore
Directory: test/Cherry Hill
Directory: test/Edison
Directory: test/New York City
Directory: test/Philadelphia
Directory: test/this is a dirname with quotes, lfs, escapes: "\''
'
\e\n\d
$ ./test.sh "Cherry Hill" "New York City"
Directory: Cherry Hill
Directory: New York City
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-07-21 21:50:22
find . -print0|while read -d $'\0' file; do echo "$file"; done
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-03-15 17:45:46
Ps jeśli chodzi tylko o spację na wejściu, to niektóre podwójne cudzysłowy działały mi bez problemu...
read artist;
find "/mnt/2tb_USB_hard_disc/p_music/$artist" -type f -name *.mp3 -exec mpg123 '{}' \;
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-11-03 23:48:30
Można użyć IFS (wewnętrznego separatora pól) tymczasowo używając :
OLD_IFS=$IFS # Stores Default IFS
IFS=$'\n' # Set it to line break
for f in `find test/* -type d`; do
echo $f
done
$IFS=$OLD_IFS
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-01-09 23:16:00
Aby dodać do tego, coJonathan powiedział: użyj opcji -print0
dla find
w połączeniu z xargs
w następujący sposób:
find test/* -type d -print0 | xargs -0 command
, które wykona polecenie command
z odpowiednimi argumentami; katalogi ze spacjami będą poprawnie cytowane (tzn. będą przekazywane jako jeden argument).
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:10:36
#!/bin/bash
dirtys=()
for folder in *
do
if [ -d "$folder" ]; then
dirtys=("${dirtys[@]}" "$folder")
fi
done
for dir in "${dirtys[@]}"
do
for file in "$dir"/\*.mov # <== *.mov
do
#dir_e=`echo "$dir" | sed 's/[[:space:]]/\\\ /g'` -- This line will replace each space into '\ '
out=`echo "$file" | sed 's/\(.*\)\/\(.*\)/\2/'` # These two line code can be written in one line using multiple sed commands.
out=`echo "$out" | sed 's/[[:space:]]/_/g'`
#echo "ffmpeg -i $out_e -sameq -vcodec msmpeg4v2 -acodec pcm_u8 $dir_e/${out/%mov/avi}"
`ffmpeg -i "$file" -sameq -vcodec msmpeg4v2 -acodec pcm_u8 "$dir"/${out/%mov/avi}`
done
done
Powyższy kod zostanie przekonwertowany .pliki mov do .avi. The .pliki mov znajdują się w różnych folderach i nazwy folderów mają również białe spacje . Mój powyższy skrypt przekonwertuje .pliki mov do .plik avi w tym samym folderze. Nie wiem, czy wam to pomoże.
Case:
[sony@localhost shell_tutorial]$ ls
Chapter 01 - Introduction Chapter 02 - Your First Shell Script
[sony@localhost shell_tutorial]$ cd Chapter\ 01\ -\ Introduction/
[sony@localhost Chapter 01 - Introduction]$ ls
0101 - About this Course.mov 0102 - Course Structure.mov
[sony@localhost Chapter 01 - Introduction]$ ./above_script
... successfully executed.
[sony@localhost Chapter 01 - Introduction]$ ls
0101_-_About_this_Course.avi 0102_-_Course_Structure.avi
0101 - About this Course.mov 0102 - Course Structure.mov
[sony@localhost Chapter 01 - Introduction]$ CHEERS!
Zdrówko!Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-11-18 12:21:19
Musiał też radzić sobie z białymi spacjami w nazwach ścieżek. W końcu użyłem rekurencji i for item in /path/*
:
function recursedir {
local item
for item in "${1%/}"/*
do
if [ -d "$item" ]
then
recursedir "$item"
else
command
fi
done
}
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-02-24 17:46:38
Konwertuj listę plików na tablicę Bash. Wykorzystuje to 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 Rezultatem jest sposób na konwersję dowolnego wejścia wielowierszowego na tablicę Bash.
#!/bin/bash
# This is the command where we want to convert the output to an array.
# Output is: fileSize fileNameIncludingPath
multiLineCommand="find . -mindepth 1 -printf '%s %p\\n'"
# This eval converts the multi-line output of multiLineCommand to a
# Bash array. To convert stdin, remove: < <(eval "$multiLineCommand" )
eval "declare -a myArray=`( arr=(); while read -r line; do arr[${#arr[@]}]="$line"; done; declare -p arr | sed -e 's/^declare -a arr=//' ) < <(eval "$multiLineCommand" )`"
for f in "${myArray[@]}"
do
echo "Element: $f"
done
To podejście wydaje się działać nawet wtedy, gdy występują złe znaki i jest ogólnym sposobem konwersji dowolnego wejścia na tablicę Bash. Wadą jest to, że jeśli wejście jest długie, możesz przekrocz limity rozmiaru wiersza poleceń Basha lub zużyj duże ilości pamięci.
Podejścia, w których pętla, która ostatecznie pracuje nad listą, również ma wstawioną listę, mają tę wadę, że odczyt stdin nie jest łatwy( np. Prośba użytkownika o wejście), a pętla jest nowym procesem, więc możesz się zastanawiać, dlaczego zmienne ustawione wewnątrz pętli nie są dostępne po jej zakończeniu.
Nie lubię też ustawiania IFS, może to zepsuć inny kod.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-09-26 15:09:50
Właśnie odkryłem, że są pewne podobieństwa między moim pytaniem i Twoim. Jeśli chcesz przekazać argumenty do komend
test.sh "Cherry Hill" "New York City"
Aby wydrukować je w kolejności
for SOME_ARG in "$@"
do
echo "$SOME_ARG";
done;
Zauważ, że $@ jest otoczony podwójnymi cudzysłowami, niektóre notatki tutaj
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:10:36
Potrzebowałem tej samej koncepcji, aby skompresować kolejno kilka katalogów lub plików z określonego folderu. Rozwiązałem używając awk do parsela listy z ls i aby uniknąć problemu pustych spacji w nazwie.
source="/xxx/xxx"
dest="/yyy/yyy"
n_max=`ls . | wc -l`
echo "Loop over items..."
i=1
while [ $i -le $n_max ];do
item=`ls . | awk 'NR=='$i'' `
echo "File selected for compression: $item"
tar -cvzf $dest/"$item".tar.gz "$item"
i=$(( i + 1 ))
done
echo "Done!!!"
Co o tym myślisz?Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-08-24 19:47:17
find Downloads -type f | while read file; do printf "%q\n" "$file"; done
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-01-15 09:57:38
Widzę zbyt wiele skomplikowanych odpowiedzi. Nie chcę przekazywać wyjścia 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 białą spację.
Tak to rozwiązałem:
find . -name \*.dbf -print0 -exec mv '{}' . ';'
Wygląda dla mnie bardzo prosto
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-04-11 12:54:30
Dla mnie to działa i jest w zasadzie "czyste":
for f in "$(find ./test -type d)" ; do
echo "$f"
done
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-09-19 17:28:21
Miałem tylko prosty problem z wariantem... Konwertuj pliki wpisane .flv do. mp3 (ziewanie).
for file in read `find . *.flv`; do ffmpeg -i ${file} -acodec copy ${file}.mp3;done
Rekurencyjnie Znajdź wszystkie pliki Flash użytkownika Macintosh i przekształć je w audio (Kopiuj, bez transkodowania) ... to tak jak wyżej, zauważając, że czytać zamiast tylko ' dla pliku w ' ucieknie.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-04-21 19:19:28