Błąd skryptu Bash z łańcuchami ze ścieżkami, które mają spacje i symbole wieloznaczne

25

Mam problem z opanowaniem podstaw skryptów Bash. Oto co mam do tej pory:

#!/bin/bash
FILES="/home/john/my directory/*.txt"

for f in "${FILES}"
do
  echo "${f}"
done

Chcę tylko wyświetlić listę wszystkich .txtplików w forpętli, dzięki czemu mogę robić z nimi różne rzeczy. Ale miejsce w my directorygwiazdce i gwiazdka po *.txtprostu nie grają ładnie. Próbowałem używać go z podwójnymi cudzysłowami i bez nich, z nawiasami klamrowymi i bez nich na nazwach zmiennych i nadal nie mogę wydrukować wszystkich .txtplików.

To bardzo podstawowa rzecz, ale wciąż walczę, ponieważ jestem zmęczony i nie mogę myśleć prosto.

Co ja robię źle?

Udało mi się z powodzeniem zastosować powyższy skrypt, jeśli moje PLIKI nie mają spacji ani gwiazdki ... Musiałem eksperymentować z podwójnymi cudzysłowami i nawiasami klamrowymi lub bez nich, aby go uruchomić. Ale w momencie gdy mam zarówno spacje, jak i gwiazdkę, wszystko to psuje.

Jan
źródło

Odpowiedzi:

33

Wewnątrz cytatów *nie zostanie rozwinięta lista plików. Aby pomyślnie użyć takiego symbolu wieloznacznego, musi on znajdować się poza cudzysłowami.

Nawet jeśli symbol wieloznaczny rozwinąłby się, wyrażenie "${FILES}"spowodowałoby pojedynczy ciąg, a nie listę plików.

Jednym z podejść, które by działało, byłoby:

#!/bin/bash
DIR="/home/john/my directory/"
for f in "$DIR"/*.txt
do
  echo "${f}"
done

Powyżej nazwy plików ze spacjami lub innymi trudnymi znakami będą obsługiwane poprawnie.

Bardziej zaawansowane podejście może wykorzystywać tablice bash:

#!/bin/bash
FILES=("/home/john/my directory/"*.txt)
for f in "${FILES[@]}"
do
  echo "${f}"
done

W tym przypadku FILESjest tablica nazw plików. Pareny otaczające definicję tworzą tablicę. Pamiętaj, że *poza cytatami. Konstrukcja "${FILES[@]}"jest szczególnym przypadkiem: zostanie rozwinięta do listy ciągów, gdzie każdy ciąg jest jedną z nazw plików. Nazwy plików ze spacjami lub innymi trudnymi znakami będą obsługiwane poprawnie.

John1024
źródło
1
świetnie, że zadziałało
Jan
Warto zauważyć, że jeśli przepuszczasz takie ścieżki przez funkcje, musisz upewnić się, że cytujesz zmienną samodzielnie, a nie konkatenuje ją jako część większego łańcucha: for f in "$DIR"/*.txt= fine for f in "$DIR/*.txt"= breaks
pospi
7

Podczas gdy używanie tablic, jak pokazuje John1024, ma o wiele większy sens, tutaj możesz również użyć operatora split + glob (pozostawiając cudzysłowu zmienną niecytowaną).

Ponieważ chcesz tylko część globalną tego operatora, musisz wyłączyć część podzieloną :

#! /bin/sh -
# that also works in any sh, so you don't even need to have or use bash

file_pattern="/home/john/my directory/*.txt"
# all uppercase variables should be reserved for environment variables

IFS='' # disable splitting

for f in $file_pattern # here we're not quoting the variable so
                       # we're invoking the split+glob operator.
do
  printf '%s\n' "$f" # avoid the non-reliable, non-portable "echo"
done
Stéphane Chazelas
źródło
0

Możesz pozostawić tylko znaki wieloznaczne poza cudzysłowami.
Coś w rodzaju:
w przypadku „plików ze spacjami” *.. Txt „
wykonaj
przetwarzanie
zakończone
Jeśli same symbole wieloznaczne rozwiną się do spacji, musisz użyć pliku dla linii, na przykład użyj ls -l, aby wygenerować listę pliki i użyj bash read, aby uzyskać każdy plik.

Marcelo Pacheco
źródło
-1

Kiedy chcesz przetwarzać na zestawie plików, bierzesz pod uwagę, że pod nazwą może znajdować się spacja lub inny kod scape, więc przed rozpoczęciem procesu, np. for loopLub find commandustaw na IFS bash env variable:

IFS=$(echo -en "\n\b")
Zatoka Perska
źródło