Mustervergleich für Pfadnamen in Bash

10

Ich möchte auf eine Liste von Unterverzeichnissen in einem Verzeichnis reagieren. Erwägen:

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

Das gibt

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

Aber ich will Werte , dass entspricht dem zweiten Teil des Weges elf, glusw. Ich weiß , wie die führende abzustreifen x86-headers.

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

was gibt

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

Ich weiß auch, wie ich spätere Begriffe auf dem Weg entfernen kann. Dh eine Ebene runter gehen:

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

gibt

elf
gl
gmp
gnome2
gtk2
jni
libc

Der Versuch, diese zu kombinieren, funktioniert jedoch nicht. Dh

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

gibt

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

Zweifellos ist dies eine falsche Syntax, aber ich kenne die richtige Syntax nicht. Natürlich könnte es bessere Möglichkeiten geben, dies zu tun. Wenn ich Python verwenden würde, würde ich split on verwenden /, um jeden Pfad in eine Liste zu unterteilen und dann das zweite Element auszuwählen, aber ich weiß nicht, wie ich das in bash machen soll.

EDIT: Danke für die Antworten. Ich hätte auch fragen sollen, ob es möglich ist, dies portabel zu machen, und wenn ja, wie?

Faheem Mitha
quelle

Antworten:

9

Sie können sie nicht mit bash(oder POSIXly) kombinieren, sondern müssen dies in zwei Schritten tun.

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

Das ist auf jede POSIX-Shell portierbar. Wenn Sie eine Portabilität auf die Bourne-Shell benötigen (aber warum sollten Sie dann Ihre Frage / Bash markieren ?), Falls Sie auf Solaris portieren und gezwungen sind, /bin/shanstelle des Standards shdort zu portieren oder auf 20 Jahre alte Systeme zu portieren. können Sie diesen Ansatz verwenden (der auch mit POSIX-Shells funktioniert):

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

(oben ist einer der sehr seltenen Fälle, in denen es sinnvoll ist, eine Variable nicht in Anführungszeichen zu setzen).

Nur fürs Protokoll, mit zsh:

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

(die t ail des h ead der h ead der Datei).

Oder für Ihren pythonAnsatz:

x=elf/C/populate.sh
i=${${(s:/:)x}[2]}
Stéphane Chazelas
quelle
Das Semikolon in i=${x#*/}; i=${i%%/*}ist optional, oder?
Dimitre Radoulov
3
@DimitreRadoulov Es ist optional, aber einige Shells wie einige alte Versionen von ash/dash würden die Zuweisungen von rechts nach links ausführen (so hat sich die Bourne-Shell verhalten) und in diesem Fall Probleme verursachen.
Stéphane Chazelas
7

Durch die Parametererweiterung können Sie keine Ausdrücke verschachteln, daher müssen Sie dies in zwei Schritten mit einer Zuweisung tun:

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

Sie haben hier die Möglichkeit, Arrays zu verwenden, aber ich würde denken, dass dies umständlicher ist als das obige PE:

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

Dann gibt es die dritte Möglichkeit, externe Befehle zu verwenden. Bei einer kurzen Liste von Dateien ist dies normalerweise die am wenigsten effiziente Option, aber sie skaliert am besten, wenn die Anzahl der Dateien zunimmt:

# For clarity, assume no names have linebreaks in them
find . -name populate.sh | cut -d / -f 3
Kojiro
quelle
Das gilt jedoch nicht für alle Muscheln, zsherlaubt das Verschachteln.
Stéphane Chazelas
3
@StephaneChazelas fair genug, aber diese Frage ist markiert 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
quelle
2

Seien Sie sehr vorsichtig, wenn Sie ein Konstrukt wie folgt verwenden:

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

Wie Sie wollen, könnten Sie am Ende eine echo *. In diesem Fall ist es nur lustig, aber es könnte viel schlimmer sein, wenn Sie root sind, Ihr CWDis /und Sie einige Unterverzeichnisse löschen möchten, /tmpanstatt sie zu wiederholen!

Um dies in Bash zu beheben, können Sie Folgendes tun:

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

Beachten Sie das shopt -u nullglobam Ende, um das Standard-Bash-Verhalten nach der Schleife wiederherzustellen.

Eine tragbarere Lösung könnte sein:

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

(Die ##und %%Parameter-Ersetzungen sind in ksh88 gültig ).

Folgen Sie diesem Link für eine ausführlichere Diskussion von nullglob .

Beachten Sie, dass die drei oben genannten Lösungen fehlschlagen, wenn Sie ein Zwischenverzeichnis mit einem Leerzeichen im Namen haben. Verwenden Sie zur Lösung doppelte Anführungszeichen in der Variablensubstitution:

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

Um den Mustervergleich für Pfadnamen portabel durchzuführen, können Sie die IFSShell-Variable auf setzen /und dann die Shell-Parametererweiterung verwenden.

Wenn Sie dies alles in einer Subshell tun, bleibt die Umgebung der übergeordneten Shell unverändert!

# 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
quelle