Come posso stabilire a livello di codice se un nome file corrisponde a un modello glob shell?


13

Vorrei dire se una stringa $stringsarebbe abbinata a un modello glob $pattern. $stringpuò essere o meno il nome di un file esistente. Come posso fare questo?

Assumi i seguenti formati per le mie stringhe di input:

string="/foo/bar"
pattern1="/foo/*"
pattern2="/foo/{bar,baz}"

Mi piacerebbe trovare un linguaggio bash che determina se $stringsarebbe stato accompagnato da $pattern1, $pattern2o qualsiasi altro modello di glob arbitrario. Ecco cosa ho provato finora:

  1. [[ "$string" = $pattern ]]

    Funziona quasi, tranne per il fatto che $patternviene interpretato come un modello di stringa e non come un modello glob.

  2. [ "$string" = $pattern ]

    Il problema con questo approccio è che $patternviene espanso e quindi viene eseguito il confronto tra stringhe $stringe l'espansione di $pattern.

  3. [[ "$(find $pattern -print0 -maxdepth 0 2>/dev/null)" =~ "$string" ]]

    Questo funziona, ma solo se $stringcontiene un file esistente.

  4. [[ $string =~ $pattern ]]

    Questo non funziona perché l' =~operatore causa $patternl'interpretazione come un'espressione regolare estesa, non un modello glob o jolly.


2
Il problema che incontrerai è che {bar,baz}non è uno schema. È l'espansione dei parametri. La differenza sottile ma critica in ciò {bar,baz}si espande molto presto in molteplici argomenti bare baz.
Patrick,

Se la shell può espandere i parametri, sicuramente può dire se una stringa è una potenziale espansione di un glob.
Jayhendren,

provalo a = ls /foo/* ora puoi abbinarlo in a
Hackaholic il

1
@Patrick: dopo aver letto la pagina man di bash, ho imparato che in foo/{bar,baz}realtà è un'espansione di parentesi graffe (non un'espansione dei parametri) mentre foo/*è l'espansione del percorso. $stringè l'espansione dei parametri. Tutto ciò avviene in momenti diversi e con meccanismi diversi.
Jayhendren,

@jayhendren @Patrick ha ragione, e poi hai appreso che la tua domanda alla fine non è ciò che il titolo porta a credere. Piuttosto, si desidera abbinare una stringa a vari tipi di motivi. Se si desidera abbinare rigorosamente un pattern glob, l' caseistruzione esegue l'espansione Pathname ("globbing") secondo il manuale di Bash.
Mike S,

Risposte:


8

Non esiste una soluzione generale per questo problema. Il motivo è che, in bash, l'espansione del controvento (cioè l' {pattern1,pattern2,...}espansione del nome del file (ovvero i modelli glob) sono considerati cose separate ed espanse in condizioni diverse e in momenti diversi. Ecco l'elenco completo delle espansioni che bash esegue:

  • espansione parentesi graffa
  • espansione della tilde
  • espansione dei parametri e delle variabili
  • sostituzione del comando
  • espansione aritmetica
  • frazionamento di parole
  • espansione del percorso

Dal momento che ci preoccupiamo solo di un sottoinsieme di questi (forse parentesi graffa, tilde ed espansione del nome di percorso), è possibile utilizzare determinati modelli e meccanismi per limitare l'espansione in modo controllabile. Per esempio:

#!/bin/bash
set -f

string=/foo/bar

for pattern in /foo/{*,foo*,bar*,**,**/*}; do
    [[ $string == $pattern ]] && echo "$pattern matches $string"
done

L'esecuzione di questo script genera il seguente output:

/foo/* matches /foo/bar
/foo/bar* matches /foo/bar
/foo/** matches /foo/bar

Questo perché set -fdisabilita l'espansione del nome percorso, quindi nell'istruzione si verificano solo l'espansione parentesi e l'espansione tilde for pattern in /foo/{*,foo*,bar*,**,**/*}. Possiamo quindi utilizzare l'operazione [[ $string == $pattern ]]di test per verificare l'espansione del nome percorso dopo che l'espansione del controvento è già stata eseguita.


7

Non credo che {bar,baz} sia un modello glob shell (anche se certamente lo /foo/ba[rz]è) ma se vuoi sapere se le $stringpartite $patternpuoi fare:

case "$string" in 
($pattern) put your successful execution statement here;;
(*)        this is where your failure case should be   ;;
esac

Puoi fare quanti ne vuoi:

case "$string" in
($pattern1) do something;;
($pattern2) do differently;;
(*)         still no match;;
esac

1
Ciao @mikeserv, come indicato nei commenti e nella risposta che ho fornito sopra, ho già imparato che ciò che dici è vero - {bar,baz}non è un modello glob. Ho già trovato una soluzione alla mia domanda che ne tenga conto.
Jayhendren,

3
+1 Questo risponde alla domanda esattamente come indicato nel titolo e nella prima frase. sebbene la domanda in seguito unisca altri schemi con schemi glob shell.
Mike S,

3

Come ha sottolineato Patrick, hai bisogno di un "tipo diverso" di modello:

[[ /foo/bar == /foo/@(bar|baz) ]]


string="/foo/bar"
pattern="/foo/@(bar|baz)"
[[ $string == $pattern ]]

Le citazioni non sono necessarie lì.


Ok, funziona, ma a rigor di termini, non risponde alla mia domanda. Ad esempio, vorrei prendere in considerazione gli schemi che provengono da un'altra fonte, vale a dire gli schemi sono fuori dal mio controllo.
Jayhendren,

@jayhendren Quindi probabilmente devi prima convertire il modello in arrivo in quei bash accetta.
Hauke ​​Laging,

Quindi tutto ciò che hai fatto è stato trasformare la mia domanda da "come faccio a sapere se un nome file è una potenziale espansione di un'espressione" a "come faccio a convertire i normali schemi di nome file in stile bash in modelli globali estesi in stile bash".
Jayhendren,

@jayhendren Considerando che quello che vuoi sembra impossibile "tutto quello che hai fatto davvero" mi sembra un po 'strano ma forse è solo una cosa di lingua straniera. Se si desidera porre questa nuova domanda, è necessario spiegare l'aspetto dei modelli di input. Forse è un'operazione semplice sed.
Hauke ​​Laging,

1
@HaukeLaging è corretto. Le persone che vengono qui per capire come abbinarsi ai pattern glob (alias "Pathname Expansion") possono confondersi, come ho fatto io, perché il titolo dice "shell glob pattern" ma i contenuti della sua domanda usano un pattern non-glob . Ad un certo punto, Jayhendren ha appreso della differenza, ma la sua confusione iniziale è ciò che ha causato Hauke ​​a rispondere nel modo in cui ha fatto.
Mike S,

1

Vorrei usare grep così:

#!/bin/bash
string="/foo/bar"
pattern1="/foo/*"
pattern2="/foo/{bar,baz}"

if echo $string | grep -e "$pattern1" > /dev/null; then
    echo $string matches $pattern1
fi

if echo $string | grep -e "$pattern2" > /dev/null; then
    echo $string matches $pattern2
fi

Che dà l'output:

./test2.bsh 
/foo/bar matches /foo/*

-2

in zsh:

[[ $string = $~pattern ]] && print true

1
La domanda afferma che verrà utilizzata la shell bash.
Jayhendren,
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.