Dopasowywanie wzorców dla nazw ścieżek w bash

10

Chcę działać na liście podkatalogów w katalogu. Rozważać:

for x in x86-headers/*/C/populate.sh; do echo $x; done

To daje

x86-headers/elf/C/populate.sh
x86-headers/gl/C/populate.sh
x86-headers/gmp/C/populate.sh
x86-headers/gnome2/C/populate.sh
x86-headers/gtk2/C/populate.sh
x86-headers/jni/C/populate.sh
x86-headers/libc/C/populate.sh

Ale chcę wartości, która odpowiada drugiej części ścieżki elf, glitp wiem jak rozebrać wiodącym x86-headers.

for x in x86-headers/*/C/populate.sh; do i=${x##x86-headers/}; echo $i; done

co daje

elf/C/populate.sh
gl/C/populate.sh
gmp/C/populate.sh
gnome2/C/populate.sh
gtk2/C/populate.sh
jni/C/populate.sh
libc/C/populate.sh

Wiem też, jak zrzucić późniejsze warunki na ścieżce. Tj. Schodzę o jeden poziom:

cd x86-headers
for x in */C/populate.sh; do i=${x%%/*}; echo $i; done

daje

elf
gl
gmp
gnome2
gtk2
jni
libc

Ale próba połączenia ich nie działa. To znaczy

for x in x86-headers/*/C/populate.sh; do i=${${x##x86-headers}%%/*}; echo $i; done

daje

bash: ${${x##x86-headers}%%/*}: bad substitution

Bez wątpienia jest to niepoprawna składnia, ale nie znam poprawnej składni. Oczywiście mogą być na to lepsze sposoby. Gdybym używał Pythona, użyłbym podziału, /aby podzielić każdą ścieżkę na listę, a następnie wybrać drugi element, ale nie wiem, jak to zrobić w bash.

EDYCJA: Dzięki za odpowiedzi. Powinienem również zapytać, czy można to zrobić przenośnie, a jeśli tak, to w jaki sposób?

Faheem Mitha
źródło

Odpowiedzi:

9

Nie możesz ich łączyć z bash(lub POSIXly), musisz to zrobić w dwóch krokach.

i=${x#*/}; i=${i%%/*}

Jest to przenośne dla dowolnej powłoki POSIX. Jeśli potrzebujesz przenośności do powłoki Bourne'a (ale dlaczego miałbyś wtedy oznaczyć swoje pytanie / bash ?) Również w przypadku, gdy przenosisz się do systemu Solaris i jesteś zmuszony używać go /bin/shzamiast standardowego shlub do 20-letnich systemów) , możesz użyć tego podejścia (które będzie działać również z powłokami POSIX):

IFS=/; set -f
set x $x
i=$3

(powyżej jest jednym z bardzo rzadkich przypadków, w których sensowne jest pozostawienie zmiennej bez cudzysłowu).

Dla przypomnienia, zsh:

print -rl -- x86-headers/*/C/populate.sh(:h:h:t)

(The t ail o h ead o h Wifi.Prowadzić pliku).

Lub dla twojego pythonpodejścia:

x=elf/C/populate.sh
i=${${(s:/:)x}[2]}
Stéphane Chazelas
źródło
Średnik w i=${x#*/}; i=${i%%/*}jest opcjonalny, prawda?
Dimitre Radoulov
3
@DimitreRadoulov Jest opcjonalny, ale niektóre powłoki, takie jak niektóre starsze wersje ash/dash , uruchamiałyby przypisania od prawej do lewej (tak zachowywała się powłoka Bourne'a) i powodowały problem w tym przypadku.
Stéphane Chazelas
7

Rozszerzanie parametrów nie pozwala na zagnieżdżanie wyrażeń, więc musisz to zrobić w dwóch krokach, z przypisaniem:

i=${x##x86-headers/}
i=${i%%/*}

Masz tutaj opcję użycia tablic, ale sądzę, że byłoby to bardziej kłopotliwe niż powyższy PE:

IFS=/
set -f # Disable globbing in case unquoted $x has globs in it
i=( $x )
set +f
echo "${i[1]}"

Jest też trzecia opcja korzystania z zewnętrznych poleceń. Z krótką listą plików jest to zazwyczaj najmniej wydajna opcja, ale będzie skalować się najlepiej w miarę wzrostu liczby plików:

# For clarity, assume no names have linebreaks in them
find . -name populate.sh | cut -d / -f 3
kojiro
źródło
Nie dotyczy to jednak wszystkich powłok, zshpozwala na zagnieżdżanie.
Stéphane Chazelas
3
@StephaneChazelas wystarczy, ale to pytanie jest oznaczone bash.
kojiro
2
regex="x86-headers/([^/]*)/.*"
for f in x86-headers/*/C/populate.sh; do [[ $f =~ $regex ]] && echo "${BASH_REMATCH[1]}"; done
elf
gl
gmp
gnome2
gtk2
jni
libc
watael
źródło
2

Bądź bardzo ostrożny, używając poniższego konstruktu:

# What happen if no files match ?
for x in x86-headers/*/C/populate.sh; do
  x=${x##x86-headers/}
  x=${x%%/*}
  echo $x
done

Jak możesz to zrobić echo *. W tym przypadku jest to tylko zabawne, ale mogło być znacznie najgorszy jeśli są korzeń, twoje CWDznaczy /i chcesz usunąć niektóre katalogi sub /tmpzamiast je echem!

Aby to naprawić w bash, możesz:

shopt -s nullglob
for x in x86-headers/*/C/populate.sh; do
  x=${x##x86-headers/}
  x=${x%%/*}
  echo $x
done
shopt -u nullglob

Zwróć uwagę shopt -u nullglobna końcu, aby przywrócić domyślne zachowanie bash po pętli.

Bardziej przenośnym rozwiązaniem może być:

for x in x86-headers/*/C/populate.sh; do
  [ -e $x ] || break
  x=${x##x86-headers/}
  x=${x%%/*}
  echo $x
done

( Podstawienia ##i %%podstawienia parametrów są poprawne w ksh88 ).

Kliknij ten link, aby uzyskać pełniejszą dyskusję o nullglob .

Zauważ, że powyższe 3 rozwiązania zawodzą, jeśli masz katalog pośredni ze spacją w nazwie. Aby go rozwiązać, użyj podwójnych cudzysłowów w podstawieniu zmiennej:

for x in x86-headers/*/C/populate.sh; do
  [ -e "$x" ] || break
  x="${x##x86-headers/}"
  x="${x%%/*}"
  echo "$x"
done
jfg956
źródło
0

Aby przenośnie dopasowywać wzorce do nazw ścieżek, możesz ustawić IFSzmienną powłoki na, /a następnie użyć rozszerzenia parametrów powłoki.

Wykonanie tego wszystkiego w podpowłoce pomaga utrzymać środowisko powłoki nadrzędnej bez zmian!

# cf. "The real Bourne shell problem",
# http://utcc.utoronto.ca/~cks/space/blog/programming/BourneShellLists

paths='
x86-headers/elf/C/populate.sh
x86-headers/gl/C/populate.sh
x86-headers/gmp/C/populate.sh
x86-headers/gnome2/C/populate.sh
x86-headers/gtk2/C/populate.sh
x86-headers/jni/C/populate.sh
x86-headers/libc/C/populate.sh
'

IFS='
'

# version 1
for x in $paths; do 
   ( IFS='/'; set -- ${x}; echo "$2" ); 
done


# version 2
set -- ${paths}
for x in $@; do 
   ( IFS='/'; set -- ${x}; echo "$2" ); 
done
Carlo
źródło