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?


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


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"

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

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 :)


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

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.


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

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).


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

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

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

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.