Corrispondenza del modello sui nomi dei percorsi in bash


10

Voglio agire su un elenco di sottodirectory in una directory. Ritenere:

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

Questo da

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

Ma voglio valori che corrisponde alla seconda parte del percorso, elf, gl, ecc so come spogliano leader x86-headers.

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

che dà

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

So anche come eliminare i termini successivi nel percorso. Vale a dire scendere di un livello:

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

elf
gl
gmp
gnome2
gtk2
jni
libc

Ma, provare a combinare questi non funziona. ie

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

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

Non c'è dubbio che questa è sintassi errata, ma non conosco la sintassi corretta. Naturalmente ci potrebbero essere modi migliori per farlo. Se avessi usato Python, avrei usato split on /per spezzare ogni percorso in un elenco, e quindi scegliere il secondo elemento, ma non so come farlo in bash.

EDIT: grazie per le risposte. Avrei dovuto anche chiedere, è possibile farlo in modo portabile, e se sì, come?

Risposte:


9

Non puoi combinarli con bash(o POSIXly), devi farlo in due passaggi.

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

È portatile per qualsiasi shell POSIX. Se hai bisogno di portabilità sulla shell Bourne (ma perché dovresti taggare la tua domanda / bash allora?) Anche nel caso in cui stai effettuando il porting su Solaris e sei costretto ad usare al /bin/shposto dello standard shlì, o porting su sistemi di 20 anni) , è possibile utilizzare questo approccio (che funzionerà anche con le shell POSIX):

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

(sopra è uno dei casi molto rari in cui ha senso lasciare una variabile non quotata).

Solo per la cronaca, con zsh:

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

(la t ail della h ead del h ead del file).

O per il tuo pythonapproccio:

x=elf/C/populate.sh
i=${${(s:/:)x}[2]}

Il punto e virgola i=${x#*/}; i=${i%%/*}è facoltativo, giusto?
Dimitre Radoulov,

3
@DimitreRadoulov È facoltativo, ma alcune shell come alcune vecchie versioni di ash/dash eseguivano le assegnazioni da destra a sinistra (che era il comportamento della shell Bourne) e causavano problemi in questo caso.
Stéphane Chazelas,

7

L'espansione dei parametri non ti consente di nidificare le espressioni, quindi sei costretto a farlo in due passaggi, con un compito:

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

Qui hai la possibilità di usare array, ma penso che sarebbe più ingombrante del PE sopra:

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

Quindi c'è la terza opzione di usare comandi esterni. Con un breve elenco di file questa è di solito l'opzione meno efficiente, ma ridimensionerà al meglio con l'aumentare del numero di file:

# For clarity, assume no names have linebreaks in them
find . -name populate.sh | cut -d / -f 3

Ciò non è vero per tutte le shell, tuttavia zshconsente l'annidamento.
Stéphane Chazelas,

3
@StephaneChazelas è abbastanza giusto, ma questa domanda è taggata 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

2

Fai molta attenzione usando un costrutto come muggito:

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

Come puoi finire per fare un echo *. In questo caso, è solo divertente, ma potrebbe essere molto peggio se il vostro root, i tuoi CWDIS /e si desidera eliminare alcune sotto directory di /tmpinvece di eco!

Per risolvere il problema in bash, potresti fare:

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

Si noti shopt -u nullgloballa fine per ripristinare il comportamento bash predefinito dopo il ciclo.

Una soluzione più portatile potrebbe essere:

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

(Le sostituzioni di parametri ##e %%sono valide in ksh88 ).

Segui questo link per una discussione più completa su nullglob .

Si noti che le 3 soluzioni precedenti non riescono se si dispone di una directory intermedia con uno spazio nel suo nome. Per risolverlo, usa le virgolette doppie nella sostituzione della variabile:

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

0

Per eseguire la corrispondenza dei modelli sui nomi dei percorsi in modo portabile, è possibile impostare la IFSvariabile shell su /e quindi utilizzare l'espansione dei parametri della shell.

Fare tutto ciò in una subshell aiuta a mantenere invariato l'ambiente della shell genitore!

# 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
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.