Tablica Bash ze spacjami w elementach

150

Próbuję zbudować tablicę w bash nazw plików z mojego aparatu:

FILES=(2011-09-04 21.43.02.jpg
2011-09-05 10.23.14.jpg
2011-09-09 12.31.16.jpg
2011-09-11 08.43.12.jpg)

Jak widać, w środku każdej nazwy pliku znajduje się spacja.

Próbowałem zawijać każdą nazwę w cudzysłów i zapisywać spację ukośnikiem odwrotnym, ale żadne z nich nie działa.

Kiedy próbuję uzyskać dostęp do elementów tablicy, nadal traktuje przestrzeń jako elementdelimiter.

Jak prawidłowo przechwycić nazwy plików ze spacją wewnątrz nazwy?

abelenky
źródło
Czy próbowałeś dodawać pliki w staroświecki sposób? Lubisz FILES[0] = ...? (Edycja: właśnie zrobiłem; nie działa. Ciekawe).
Dan Fego
POSIX: stackoverflow.com/questions/2936922/…
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
Wszystkie odpowiedzi tutaj dotyczą mnie, używając Cygwin. Robi dziwne rzeczy, jeśli w nazwach plików są spacje, kropka. Obchodzę to, tworząc „tablicę” w pliku tekstowym zawierającą listę wszystkich elementów, z którymi chcę pracować, i iterując po liniach w pliku: Formatowanie polega na zamianie zamierzonych znaków odwrotnych wokół polecenia w nawiasach: IFS = ""; tablica = ( find . -maxdepth 1 -type f -iname \*.$1 -printf '%f\n'); dla elementu w $ {tablica [@]}; do echo $ element; gotowe
Alex Hall

Odpowiedzi:

121

Myślę, że problem może częściowo dotyczyć sposobu uzyskiwania dostępu do elementów. Jeśli zrobię coś prostego for elem in $FILES, mam ten sam problem co ty. Jeśli jednak uzyskam dostęp do tablicy za pośrednictwem jej indeksów, tak jak to, zadziała, jeśli dodam elementy numerycznie lub ze znakami ucieczki:

for ((i = 0; i < ${#FILES[@]}; i++))
do
    echo "${FILES[$i]}"
done

Każda z tych deklaracji $FILESpowinna działać:

FILES=(2011-09-04\ 21.43.02.jpg
2011-09-05\ 10.23.14.jpg
2011-09-09\ 12.31.16.jpg
2011-09-11\ 08.43.12.jpg)

lub

FILES=("2011-09-04 21.43.02.jpg"
"2011-09-05 10.23.14.jpg"
"2011-09-09 12.31.16.jpg"
"2011-09-11 08.43.12.jpg")

lub

FILES[0]="2011-09-04 21.43.02.jpg"
FILES[1]="2011-09-05 10.23.14.jpg"
FILES[2]="2011-09-09 12.31.16.jpg"
FILES[3]="2011-09-11 08.43.12.jpg"
Dan Fego
źródło
6
Zauważ, że powinieneś używać podwójnych cudzysłowów, kiedy używasz elementów tablicy (np echo "${FILES[$i]}".). Nie ma to znaczenia echo, ale będzie miało znaczenie dla wszystkiego, co używa go jako nazwy pliku.
Gordon Davisson
26
Nie ma potrzeby zapętlania indeksów, gdy można wykonać pętlę po elementach za pomocą for f in "${FILES[@]}".
Mark Edgar
10
@MarkEdgar Mam problemy z for f w $ {PLIKACH [@]}, gdy elementy tablicy mają spacje. Wygląda na to, że cała tablica jest ponownie interpretowana, a spacje dzielą istniejące elementy na dwa lub więcej elementów. Wygląda na to, że „” są bardzo ważne
Michael Shaw,
1
Co robi ostry ( #) symbol zrobić w for ((i = 0; i < ${#FILES[@]}; i++))rachunku?
Michał Vician,
4
Odpowiedziałem to sześć lat temu, ale wierzę, że to dostać licznik liczby elementów w plikach tablicy.
Dan Fego
91

Coś musi być nie tak ze sposobem uzyskiwania dostępu do elementów tablicy. Oto jak to się robi:

for elem in "${files[@]}"
...

Ze strony podręcznika bash :

Do każdego elementu tablicy można się odwołać za pomocą $ {nazwa [wskaźnik]}. ... Jeśli indeksem dolnym jest @ lub *, słowo jest rozszerzane na wszystkich członków nazwy. Te indeksy różnią się tylko wtedy, gdy słowo występuje w cudzysłowie. Jeśli słowo jest umieszczone w cudzysłowie, $ {name [*]} rozwija się do pojedynczego słowa z wartością każdego elementu tablicy oddzieloną pierwszym znakiem zmiennej specjalnej IFS, a $ {name [@]} rozszerza każdy element nazwę na osobne słowo .

Oczywiście podczas uzyskiwania dostępu do pojedynczego członka należy również używać podwójnych cudzysłowów

cp "${files[0]}" /tmp
user123444555621
źródło
3
Najczystsze i najbardziej eleganckie rozwiązanie w tej grupie, chociaż powinno powtórzyć, że każdy element zdefiniowany w tablicy powinien być cytowany.
Maverick
Chociaż odpowiedź Dana Fego jest skuteczna, jest to bardziej idiomatyczny sposób radzenia sobie z przestrzeniami w elementach.
Daniel Zhang,
3
Pochodząc z innych języków programowania, terminologia z tego fragmentu jest naprawdę trudna do zrozumienia. Dodatkowo składnia jest zaskakująca. Byłbym bardzo wdzięczny, gdybyś mógł zająć się tym trochę więcej? W szczególnościexpands to a single word with the value of each array member separated by the first character of the IFS special variable
CL22
1
Tak, zgadzam się, że podwójne cudzysłowy rozwiązują ten problem, a to jest lepsze niż inne rozwiązania. Aby dokładniej wyjaśnić - większości innym brakuje po prostu podwójnych cudzysłowów. Masz poprawną odpowiedź: for elem in "${files[@]}"podczas gdy oni mają for elem in ${files[@]}- więc spacje mylą ekspansję i próby uruchomienia poszczególnych słów.
arntg
Nie działa to dla mnie w systemie macOS 10.14.4, który używa „GNU bash, wersja 3.2.57 (1) -release (x86_64-apple-darwin18)”. Może błąd w starszej wersji basha?
Mark Ribau
43

Musisz użyć IFS, aby zatrzymać spację jako ogranicznik elementu.

FILES=("2011-09-04 21.43.02.jpg"
       "2011-09-05 10.23.14.jpg"
       "2011-09-09 12.31.16.jpg"
       "2011-09-11 08.43.12.jpg")
IFS=""
for jpg in ${FILES[*]}
do
    echo "${jpg}"
done

Jeśli chcesz się rozdzielić na podstawie. następnie po prostu wykonaj IFS = "." Mam nadzieję, że ci to pomoże :)

Khushneet
źródło
3
Musiałem przenieść IFS = "" do przed przypisaniem tablicy, ale to jest poprawna odpowiedź.
okradł
Używam kilku tablic do analizowania informacji i efekt IFS = "" działa tylko w jednej z nich. Kiedy używam IFS = "", wszystkie inne tablice przestają odpowiednio analizować. Jakieś wskazówki na ten temat?
Paulo Pedroso
Paulo, zobacz tutaj inną odpowiedź, która może być lepsza w Twoim przypadku: stackoverflow.com/a/9089186/1041319 . Nie wypróbowałem IFS = "" i wydaje się, że rozwiązuje to elegancko - ale Twój przykład pokazuje, dlaczego w niektórych przypadkach mogą wystąpić problemy. Możliwe jest ustawienie IFS = "" w pojedynczym wierszu, ale nadal może to być bardziej mylące niż inne rozwiązanie.
arntg
U mnie też zadziałało na bash. Dzięki @Khushneet szukałem go przez pół godziny ...
csonuryilmaz
Świetnie, tylko odpowiedź na tej stronie zadziałała. Ale musiałem też przesunąć IFS="" przed konstrukcją tablicy .
pkamb
13

Zgadzam się z innymi, że prawdopodobnie sposób uzyskiwania dostępu do elementów jest problemem. Cytowanie nazw plików w przypisaniu tablicy jest poprawne:

FILES=(
  "2011-09-04 21.43.02.jpg"
  "2011-09-05 10.23.14.jpg"
  "2011-09-09 12.31.16.jpg"
  "2011-09-11 08.43.12.jpg"
)

for f in "${FILES[@]}"
do
  echo "$f"
done

Użycie podwójnych cudzysłowów wokół dowolnej tablicy formularza "${FILES[@]}"powoduje podzielenie tablicy na jedno słowo na element tablicy. Poza tym nie rozdziela słów.

Używanie "${FILES[*]}"również ma specjalne znaczenie, ale łączy elementy tablicy z pierwszym znakiem $ IFS, w wyniku czego powstaje jedno słowo, które prawdopodobnie nie jest tym, czego chcesz.

Użycie gołego ${array[@]}lub ${array[*]}poddaje wynik tej ekspansji dalszemu dzieleniu na słowa, więc skończysz na słowach podzielonych na spacje (i wszystko inne $IFS) zamiast jednego słowa na element tablicy.

Używanie pętli for w stylu C jest również w porządku i pozwala uniknąć martwienia się o dzielenie słów, jeśli nie masz co do tego jasności:

for (( i = 0; i < ${#FILES[@]}; i++ ))
do
  echo "${FILES[$i]}"
done
Dean Hall
źródło
3

Ucieczka działa.

#!/bin/bash

FILES=(2011-09-04\ 21.43.02.jpg
2011-09-05\ 10.23.14.jpg
2011-09-09\ 12.31.16.jpg
2011-09-11\ 08.43.12.jpg)

echo ${FILES[0]}
echo ${FILES[1]}
echo ${FILES[2]}
echo ${FILES[3]}

Wynik:

$ ./test.sh
2011-09-04 21.43.02.jpg
2011-09-05 10.23.14.jpg
2011-09-09 12.31.16.jpg
2011-09-11 08.43.12.jpg

Cytowanie ciągów również daje taki sam wynik.

Chris Seymour
źródło
3

Gdybyś miał taką tablicę: #! / Bin / bash

Unix[0]='Debian'
Unix[1]="Red Hat"
Unix[2]='Ubuntu'
Unix[3]='Suse'

for i in $(echo ${Unix[@]});
    do echo $i;
done

Otrzymasz:

Debian
Red
Hat
Ubuntu
Suse

Nie wiem dlaczego, ale pętla rozbija spacje i umieszcza je jako osobny element, nawet jeśli otoczysz go cudzysłowami.

Aby obejść ten problem, zamiast wywoływać elementy tablicy, wywołujesz indeksy, które pobierają pełny ciąg zawinięty w cudzysłów. Musi być zawarty w cudzysłowie!

#!/bin/bash

Unix[0]='Debian'
Unix[1]='Red Hat'
Unix[2]='Ubuntu'
Unix[3]='Suse'

for i in $(echo ${!Unix[@]});
    do echo ${Unix[$i]};
done

Wtedy otrzymasz:

Debian
Red Hat
Ubuntu
Suse
Jonni2016aa
źródło
2

Nie jest to dokładna odpowiedź na problem cytowania / ucieczki z pierwotnego pytania, ale prawdopodobnie coś, co faktycznie byłoby bardziej przydatne dla operacji:

unset FILES
for f in 2011-*.jpg; do FILES+=("$f"); done
echo "${FILES[@]}"

Tam, gdzie, oczywiście, wyrażenie musiałoby być dostosowane do konkretnego wymagania (np. *.jpgDla wszystkich lub 2001-09-11*.jpgtylko dla zdjęć z określonego dnia).

TNT
źródło
0

Innym rozwiązaniem jest użycie pętli „while” zamiast pętli „for”:

index=0
while [ ${index} -lt ${#Array[@]} ]
  do
     echo ${Array[${index}]}
     index=$(( $index + 1 ))
  done
Javier Salas
źródło
0

Jeśli nie utkniesz w używaniu bash, różne sposoby obsługi spacji w nazwach plików są jedną z zalet skorupy ryby . Rozważmy katalog zawierający dwa pliki: „a b.txt” i „b c.txt”. Oto rozsądne przypuszczenie, jak przetworzyć listę plików wygenerowanych przez inne polecenie bash, ale kończy się to niepowodzeniem z powodu spacji w nazwach plików, które napotkałeś:

# bash
$ for f in $(ls *.txt); { echo $f; }
a
b.txt
b
c.txt

W fishprzypadku składnia jest prawie identyczna, ale wynik jest taki, jakiego można się spodziewać:

# fish
for f in (ls *.txt); echo $f; end
a b.txt
b c.txt

Działa inaczej, ponieważ fish dzieli wyjście poleceń na znaki nowej linii, a nie spacje.

Jeśli masz przypadek, w którym chcesz podzielić na spacje zamiast nowych linii, fishma do tego bardzo czytelną składnię:

for f in (ls *.txt | string split " "); echo $f; end
Mark Stosberg
źródło
0

Kiedyś resetowałem wartość IFS i wycofywanie po zakończeniu .

# backup IFS value
O_IFS=$IFS

# reset IFS value
IFS=""

FILES=(
"2011-09-04 21.43.02.jpg"
"2011-09-05 10.23.14.jpg"
"2011-09-09 12.31.16.jpg"
"2011-09-11 08.43.12.jpg"
)

for file in ${FILES[@]}; do
    echo ${file}
done

# rollback IFS value
IFS=${O_IFS}

Możliwe wyjście z pętli:

2011-09-04 21.43.02.jpg

2011-09-05 10.23.14.jpg

2011-09-09 12.31.16.jpg

2011-09-11 08.43.12.jpg

Madan Sapkota
źródło