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
Author: John Bachir, 2008-11-19

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
 100
Author: Charles Duffy,
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: '
 25
Author: Johannes Schaub - litb,
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
 20
Author: cbliard,
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" /

 7
Author: Jonathan Leffler,
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 na
 4
Author: oshunluvr,
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-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
 4
Author: murpel,
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
 4
Author: Gordon Davisson,
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
 3
Author: Freakus,
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 '{}' \;
 3
Author: hardbutnot,
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

 3
Author: amazingthere,
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).

 2
Author: Adam Rosenfield,
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!
 1
Author: Sony George,
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
}
 1
Author: Gilles,
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.

 1
Author: Steve Zobell,
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

 0
Author: Jeffrey04,
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?
 0
Author: Hìr0,
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
 0
Author: Johan Kasselman,
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

 0
Author: Tebe,
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
 -3
Author: AndrzejP,
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.

 -4
Author: mark washeim,
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