Corrispondenza regex non avida (riluttante) in sed?


407

Sto cercando di usare sed per ripulire le linee di URL per estrarre solo il dominio.

Quindi da:

http://www.suepearson.co.uk/product/174/71/3816/

Voglio:

http://www.suepearson.co.uk/

(con o senza la barra finale, non importa)

Ho provato:

 sed 's|\(http:\/\/.*?\/\).*|\1|'

e (sfuggire al quantificatore non avido)

sed 's|\(http:\/\/.*\?\/\).*|\1|'

ma non riesco a far funzionare il quantificatore non avido ( ?), quindi finisce sempre per abbinare l'intera stringa.


54
Una nota a margine: se delimiti le tue regex con "|", non devi sfuggire ai "/". In effetti, molte persone delimitano con "|" invece di "/" s per evitare i "picchetti".
AttishOculus,

12
@AttishOculus Il primo carattere dopo la 's' in un'espressione sostitutiva in sed è il delimitatore. Da qui '^ foo ^ bar ^' o 's! Foo! Bar!' funziona anche
Squidly,

1
Per regex esteso, utilizzare sed -E 's.... Tuttavia, nessun operatore riluttante.
Ondra Žižka,

Non rispondere al titolo della domanda, ma in questo caso specifico cut -d'/' -f1-3funziona semplicemente .
Petr Javorik,

Risposte:


422

Né regex Posix / GNU di base né esteso riconosce il quantificatore non avido; hai bisogno di una regex successiva. Fortunatamente, la regex del Perl per questo contesto è abbastanza facile da ottenere:

perl -pe 's|(http://.*?/).*|\1|'

13
Per farlo sul posto usa le opzioni -pi -e.
reallynice

12
Fumo santo Non posso credere che abbia funzionato :-) L'unica cosa che fa schifo è che ora il mio script ha una dipendenza Perl :-(
Tra i lati positivi

7
@Freedom_Ben: IIRC perlè richiesto da POSIX
MestreLion il

4
@ dolphus333: "Né regex Posix / GNU di base né esteso riconosce il quantificatore non avido" significa "non è possibile utilizzare il quantificatore non avido in sed".
caos,

3
@Sérgio è come fai la cosa richiesta, il che è impossibile sed, usando una sintassi sostanzialmente identica a quella dised
caos

251

In questo caso specifico, puoi fare il lavoro senza usare una regex non avida.

Prova questa regex non golosa [^/]*invece di .*?:

sed 's|\(http://[^/]*/\).*|\1|g'

3
Come rendere sed abbinare una frase non golosa usando questa tecnica?
user3694243

6
Sfortunatamente non puoi; vedi la risposta del caos .
Daniel H,

Mille grazie ... poiché perl non è più nella base di installazione predefinita in molte distribuzioni di Linux!
st0ne,


@DanielH In effetti è possibile abbinare frasi non avidamente usando questa tecnica come richiesto. Potrebbe bastare un po 'di fatica per scrivere entrambi i pattern con sufficiente precisione. Ad esempio, quando si analizza un'assegnazione valore-chiave nella query di un URL, potrebbe essere necessario ricercare l'assegnazione utilizzando ([^&=#]+)=([^&#]*). Ci sono casi che non funzionano in questo modo, ad esempio quando si analizza l'URL per la parte host e il percorso con la barra finale considerata facoltativa da escludere dalla cattura:^(http:\/\/.+?)/?$
Thomas Urban

121

Con sed, di solito eseguo una ricerca non avida cercando qualsiasi cosa tranne il separatore fino al separatore:

echo "http://www.suon.co.uk/product/1/7/3/" | sed -n 's;\(http://[^/]*\)/.*;\1;p'

Produzione:

http://www.suon.co.uk

questo è:

  • non emettere -n
  • cerca, abbina pattern, sostituisci e stampa s/<pattern>/<replace>/p
  • usa il ;separatore dei comandi di ricerca anziché /per semplificare la digitaziones;<pattern>;<replace>;p
  • ricorda la corrispondenza tra parentesi \(... \), successivamente accessibile con \1, \2...
  • incontro http://
  • seguito da qualcosa tra parentesi [], [ab/]significherebbe ao bo/
  • prima ^in []mezzo not, quindi seguito da tutto tranne che dalla cosa nel[]
  • quindi [^/]significa qualsiasi cosa tranne il /personaggio
  • *è ripetere il gruppo precedente quindi [^/]*significa caratteri tranne /.
  • finora sed -n 's;\(http://[^/]*\)significa cercare e ricordare http://seguito da tutti i caratteri tranne /e ricordare ciò che hai trovato
  • vogliamo cercare fino alla fine del dominio, quindi fermati al successivo, /quindi aggiungine un altro /alla fine: sed -n 's;\(http://[^/]*\)/'ma vogliamo abbinare il resto della riga dopo il dominio, quindi aggiungi.*
  • ora la corrispondenza ricordata nel gruppo 1 ( \1) è il dominio, quindi sostituisci la riga corrispondente con elementi salvati nel gruppo \1e stampa:sed -n 's;\(http://[^/]*\)/.*;\1;p'

Se vuoi includere anche la barra rovesciata dopo il dominio, aggiungi un'altra barra rovesciata nel gruppo per ricordare:

echo "http://www.suon.co.uk/product/1/7/3/" | sed -n 's;\(http://[^/]*/\).*;\1;p'

produzione:

http://www.suon.co.uk/

8
Per quanto riguarda le modifiche recenti: le parentesi sono una sorta di carattere tra parentesi, quindi non è errato chiamarle parentesi, specialmente se segui la parola con i caratteri reali, come ha fatto l'autore. Inoltre, è l'uso preferito in alcune culture, quindi sostituirlo con l'uso preferito nella propria cultura sembra un po 'maleducato, anche se sono sicuro che non è quello che intendeva l'editor. Personalmente, penso che sia meglio usare nomi puramente descrittivi come parentesi tonde , parentesi quadre e parentesi angolari .
Alan Moore,

2
È possibile sostituire il separatore con una stringa?
Calculemus,

37

sed non supporta l'operatore "non avido".

Devi usare l'operatore "[]" per escludere "/" dalla corrispondenza.

sed 's,\(http://[^/]*\)/.*,\1,'

PS non è necessario eseguire il backslash "/".


non proprio. se il delimitatore potrebbe essere uno dei tanti caratteri possibili (diciamo solo una stringa di numeri) la tua corrispondenza di negazione potrebbe diventare sempre più complessa. va bene, ma sarebbe sicuramente bello avere un'opzione da fare. * non avido
gesell

1
La domanda era più generale. Queste soluzioni funzionano per gli URL ma non (ad es.) Per il mio caso d'uso di eliminazione degli zeri finali. s/([[:digit:]]\.[[1-9]]*)0*/\1/ovviamente non funzionerebbe bene per 1.20300. Poiché la domanda originale riguardava gli URL, tuttavia, dovrebbero essere menzionati nella risposta accettata.
Daniel H,

33

Simulazione di un quantificatore pigro (non avido) in sed

E tutti gli altri sapori regex!

  1. Trovare la prima occorrenza di un'espressione:

    • POSIX ERE (usando l' -ropzione)

      regex:

      (EXPRESSION).*|.

      sed:

      sed -r 's/(EXPRESSION).*|./\1/g' # Global `g` modifier should be on

      Esempio (ricerca della prima sequenza di cifre) Demo live :

      $ sed -r 's/([0-9]+).*|./\1/g' <<< 'foo 12 bar 34'
      12

      Come funziona ?

      Questa regex beneficia di un'alternanza |. In ogni posizione il motore cerca di scegliere la corrispondenza più lunga (questo è uno standard POSIX seguito anche da un paio di altri motori), il che significa che va avanti .fino a quando non viene trovata una corrispondenza ([0-9]+).*. Ma anche l'ordine è importante.

      inserisci qui la descrizione dell'immagine

      Dato che il flag globale è impostato, il motore cerca di continuare ad abbinare carattere per carattere fino alla fine della stringa di input o del nostro target. Non appena il primo e unico gruppo di acquisizione del lato sinistro dell'alternanza viene abbinato, anche il (EXPRESSION)resto della linea viene consumato immediatamente .*. Manteniamo il nostro valore nel primo gruppo di acquisizione.

    • POSIX BRE

      regex:

      \(\(\(EXPRESSION\).*\)*.\)*

      sed:

      sed 's/\(\(\(EXPRESSION\).*\)*.\)*/\3/'

      Esempio (ricerca della prima sequenza di cifre):

      $ sed 's/\(\(\([0-9]\{1,\}\).*\)*.\)*/\3/' <<< 'foo 12 bar 34'
      12

      Questo è come la versione ERE ma senza alternanza. È tutto. Ad ogni singola posizione il motore cerca di far corrispondere una cifra.

      inserisci qui la descrizione dell'immagine

      Se viene trovato, le altre cifre seguenti vengono consumate e catturate e il resto della riga viene immediatamente associato altrimenti altrimenti poiché *significa più o zero salta sul secondo gruppo di acquisizione \(\([0-9]\{1,\}\).*\)*e arriva a un punto .per abbinare un singolo carattere e questo processo continua.

  2. Trovare la prima occorrenza di un'espressione delimitata :

    Questo approccio corrisponderà alla prima occorrenza di una stringa delimitata. Possiamo chiamarlo un blocco di stringhe.

    sed 's/\(END-DELIMITER-EXPRESSION\).*/\1/; \
         s/\(\(START-DELIMITER-EXPRESSION.*\)*.\)*/\1/g'

    Stringa di input:

    foobar start block #1 end barfoo start block #2 end

    -EDE: end

    -SDE: start

    $ sed 's/\(end\).*/\1/; s/\(\(start.*\)*.\)*/\1/g'

    Produzione:

    start block #1 end

    First regex \(end\).*abbina e cattura il delimitatore della prima estremità ende sostituisce tutti i match con i caratteri catturati recenti che è il delimitatore della fine. In questa fase la nostra produzione è: foobar start block #1 end.

    inserisci qui la descrizione dell'immagine

    Quindi il risultato viene passato al secondo regex \(\(start.*\)*.\)*che è lo stesso della versione POSIX BRE sopra. Corrisponde a un singolo carattere se il delimitatore iniziale startnon corrisponde, altrimenti corrisponde e acquisisce il delimitatore iniziale e corrisponde al resto dei caratteri.

    inserisci qui la descrizione dell'immagine


Rispondere direttamente alla tua domanda

Utilizzando l'approccio n. 2 (espressione delimitata) è necessario selezionare due espressioni appropriate:

  • EDE: [^:/]\/

  • SDE: http:

Uso:

$ sed 's/\([^:/]\/\).*/\1/g; s/\(\(http:.*\)*.\)*/\1/' <<< 'http://www.suepearson.co.uk/product/174/71/3816/'

Produzione:

http://www.suepearson.co.uk/

Nota: questo non funzionerà con delimitatori identici.


3) mentre suggerisci siti come regex101 per la demo, aggiungi una nota che non è sempre adatto agli strumenti di cli a causa della sintassi e delle differenze di funzionalità
Sundeep

1
@Sundeep Grazie. Ho trasformato tutte quelle citazioni in virgolette singole. Inoltre ho considerato la regola della partita più lunga a sinistra da menzionare. Tuttavia in sedtutti gli altri motori che seguono lo stesso ordine standard è importante quando si tratta di uguaglianza. Quindi echo 'foo 1' | sed -r 's/.|([0-9]+).*/\1/g'non ha una corrispondenza ma lo echo 'foo 1' | sed -r 's/([0-9]+).*|./\1/g'fa.
revo

@Sundeep anche la soluzione alternativa per le espressioni delimitate non funzionava con identici delimitatori di inizio e fine per i quali ho aggiunto una nota.
revo

ottimo punto su cosa succede quando diverse alternanze iniziano dalla stessa posizione e hanno la stessa lunghezza, immagino che seguiranno l'ordine da sinistra a destra come altri motori .. è necessario cercare se ciò è descritto nel manuale
Sundeep


20

Soluzione non avida per più di un singolo personaggio

Questo thread è davvero vecchio ma presumo che la gente ne abbia ancora bisogno. Diciamo che vuoi uccidere tutto fino al primo episodio di HELLO. Non puoi dire [^HELLO]...

Quindi una buona soluzione prevede due passaggi, supponendo che tu possa risparmiare una parola unica che non ti aspetti dall'input, diciamo top_sekrit.

In questo caso possiamo:

s/HELLO/top_sekrit/     #will only replace the very first occurrence
s/.*top_sekrit//        #kill everything till end of the first HELLO

Ovviamente, con un input più semplice potresti usare una parola più piccola, o forse anche un singolo carattere.

HTH!


4
Per renderlo ancora migliore, utile in situazioni in cui non puoi aspettarti un carattere non usato: 1. sostituisci quel carattere speciale con WORD davvero inutilizzato, 2. sostituisci la sequenza finale con il carattere speciale, 3. fai la ricerca che termina con un carattere speciale, 4 Sostituisci carattere speciale indietro, 5. Sostituisci parola speciale indietro. Ad esempio, vuoi un avido operatore tra <hello> e </hello>:
Jakub

3
Qui esempio: echo "Trova: <hello> fir ~ st <br> yes </hello> <hello> sec ~ ond </hello>" | sed -e "s, ~, VERYSPECIAL, g" -e "s, </hello>, ~, g" -e "s,. * Trova: <hello> ([^ ~] *). *, \ 1 , "-e" s, \ ~, </hello>, "-e" s, VERYSPECIAL, ~, "
Jakub

2
Sono d'accordo. bella soluzione. Vorrei riformulare il commento dicendo: se non puoi fare affidamento su ~ essere inutilizzato, sostituisci prima le occorrenze attuali usando s / ~ / VERYspeciaL / g, quindi fai il trucco sopra, quindi restituisci l'originale ~ usando s / VERYspeciaL / ~ / g
Ishahak,

1
Tendo a usare le "variabili" più rare per questo tipo di cose, quindi invece di `, userei <$$>(poiché si $$espande al tuo ID di processo nella shell, anche se dovresti usare virgolette doppie anziché singole, e che potrebbe rompere altre parti del tuo regex) o, se unicode è disponibile, qualcosa del genere <∈∋>.
Adam Katz,

Ad un certo punto devi chiederti perché non stai semplicemente usando perlo pythono un'altra lingua invece. perllo fa in modo meno fragile in una sola riga ...
ArtOfWarfare il

18

sed - abbinamento non goloso di Christoph Sieghart

Il trucco per ottenere una corrispondenza non avida in sed è quello di abbinare tutti i personaggi escluso quello che termina la partita. Lo so, un gioco da ragazzi, ma ho sprecato minuti preziosi su di esso e gli script di shell dovrebbero essere, dopo tutto, facili e veloci. Quindi nel caso qualcun altro ne avesse bisogno:

Abbinamento goloso

% echo "<b>foo</b>bar" | sed 's/<.*>//g'
bar

Abbinamento non avido

% echo "<b>foo</b>bar" | sed 's/<[^>]*>//g'
foobar

17

Questo può essere fatto usando cut:

echo "http://www.suepearson.co.uk/product/174/71/3816/" | cut -d'/' -f1-3

9

un altro modo, non usando regex, è usare il metodo dei campi / delimitatori es

string="http://www.suepearson.co.uk/product/174/71/3816/"
echo $string | awk -F"/" '{print $1,$2,$3}' OFS="/"

5

sed ha sicuramente il suo posto, ma questo non è uno di questi!

Come ha sottolineato Dee: basta usare cut. In questo caso è molto più semplice e molto più sicuro. Ecco un esempio in cui estraiamo vari componenti dall'URL usando la sintassi di Bash:

url="http://www.suepearson.co.uk/product/174/71/3816/"

protocol=$(echo "$url" | cut -d':' -f1)
host=$(echo "$url" | cut -d'/' -f3)
urlhost=$(echo "$url" | cut -d'/' -f1-3)
urlpath=$(echo "$url" | cut -d'/' -f4-)

ti dà:

protocol = "http"
host = "www.suepearson.co.uk"
urlhost = "http://www.suepearson.co.uk"
urlpath = "product/174/71/3816/"

Come puoi vedere, questo è un approccio molto più flessibile.

(tutti i crediti a Dee)



3

sed -E interpreta le espressioni regolari come espressioni regolari (moderne) estese

Aggiornamento: -E su MacOS X, -r in GNU sed.


4
No, non ... Almeno non GNU sed.
Michel de Ruiter,

7
Più in generale, -Eè univoco per BSD sede quindi per OS X. Collegamenti a pagine man. -rporta espressioni regolari estese a GNUsed come notato nella correzione di @ stephancheg. Fai attenzione quando usi un comando di variabilità nota tra le distribuzioni 'nix. L'ho imparato nel modo più duro.
fny

1
Questa è la risposta corretta se si desidera utilizzare sed ed è la più applicabile alla domanda iniziale.
Will Tice,

8
L' -ropzione GNU sed modifica solo le regole di escape, in base al Appendix A Extended regular expressionsfile di informazioni e ad alcuni test rapidi; in realtà non aggiunge un qualificatore non avido ( GNU sed version 4.2.1almeno come minimo)
eichin

1
GNU sed è stata riconosciuta -Ecome opzione non documentata per un po ', ma nella versione 4.2.2.177 , la documentazione è stata aggiornata per riflettere ciò, quindi -Eper ora va bene per entrambi.
Benjamin W.

3

C'è ancora speranza di risolverlo usando sed (GNU) puro. Nonostante ciò non sia una soluzione generica in alcuni casi è possibile utilizzare "loop" per eliminare tutte le parti non necessarie della stringa in questo modo:

sed -r -e ":loop" -e 's|(http://.+)/.*|\1|' -e "t loop"
  • -r: usa regex esteso (per parentesi + e senza caratteri di escape)
  • ": loop": definisce una nuova etichetta chiamata "loop"
  • -e: aggiunge comandi a sed
  • "t loop": torna all'etichetta "loop" in caso di sostituzione riuscita

L'unico problema qui è che taglierà anche l'ultimo carattere di separazione ('/'), ma se ne hai davvero bisogno puoi semplicemente rimetterlo al termine del "loop", basta aggiungere questo comando aggiuntivo alla fine del precedente riga di comando:

-e "s,$,/,"

2

Dato che hai dichiarato espressamente che stai cercando di usare sed (anziché perl, cut, ecc.), Prova a raggruppare. Ciò elude l'identificatore non avido che potenzialmente non viene riconosciuto. Il primo gruppo è il protocollo (ovvero "http: //", "https: //", "tcp: //", ecc.). Il secondo gruppo è il dominio:

echo "http://www.suon.co.uk/product/1/7/3/" | sed "s | ^ \ (. * // \) \ ([^ /] * \). * $ | \ 1 \ 2 |"

Se non hai familiarità con il raggruppamento, inizia qui .


1

Mi rendo conto che questa è una vecchia voce, ma qualcuno potrebbe trovarla utile. Poiché il nome di dominio completo non può superare una lunghezza totale di 253 caratteri, sostituire. * Con. \ {1, 255 \}


1

Ecco come eseguire in modo affidabile la corrispondenza non avida delle stringhe multi-carattere utilizzando sed. Diciamo che si desidera cambiare ogni foo...barper <foo...bar>così per esempio questo ingresso:

$ cat file
ABC foo DEF bar GHI foo KLM bar NOP foo QRS bar TUV

dovrebbe diventare questo output:

ABC <foo DEF bar> GHI <foo KLM bar> NOP <foo QRS bar> TUV

Per fare ciò, converti foo e bar in singoli personaggi e poi usa la negazione di quei personaggi tra loro:

$ sed 's/@/@A/g; s/{/@B/g; s/}/@C/g; s/foo/{/g; s/bar/}/g; s/{[^{}]*}/<&>/g; s/}/bar/g; s/{/foo/g; s/@C/}/g; s/@B/{/g; s/@A/@/g' file
ABC <foo DEF bar> GHI <foo KLM bar> NOP <foo QRS bar> TUV

In quanto sopra:

  1. s/@/@A/g; s/{/@B/g; s/}/@C/gsta convertendo {e }stringhe segnaposto che non possono esistere nell'input, quindi quei caratteri sono disponibili per la conversione fooebar in.
  2. s/foo/{/g; s/bar/}/gsta convertendo fooe barin {e} rispettivamente
  3. s/{[^{}]*}/<&>/gsta eseguendo l'operazione che vogliamo - la conversione foo...barin<foo...bar>
  4. s/}/bar/g; s/{/foo/gsta convertendo {e }torna a fooebar .
  5. s/@C/}/g; s/@B/{/g; s/@A/@/g sta convertendo le stringhe segnaposto ai loro caratteri originali.

Si noti che quanto sopra non si basa sul fatto che nessuna stringa particolare sia presente nell'input in quanto produce tali stringhe nel primo passaggio, né si preoccupa dell'occorrenza di una particolare regexp che si desidera abbinare poiché è possibile utilizzare {[^{}]*}tutte le volte necessarie nell'espressione per isolare la corrispondenza effettiva desiderata e / o con l'operatore di corrispondenza numerica seds, ad esempio per sostituire solo la seconda occorrenza:

$ sed 's/@/@A/g; s/{/@B/g; s/}/@C/g; s/foo/{/g; s/bar/}/g; s/{[^{}]*}/<&>/2; s/}/bar/g; s/{/foo/g; s/@C/}/g; s/@B/{/g; s/@A/@/g' file
ABC foo DEF bar GHI <foo KLM bar> NOP foo QRS bar TUV

1

Non ho ancora visto questa risposta, quindi ecco come puoi farlo con vio vim:

vi -c '%s/\(http:\/\/.\{-}\/\).*/\1/ge | wq' file &>/dev/null

Questo esegue la vi :%ssostituzione a livello globale (il trailing g), si astiene dal generare un errore se il pattern non viene trovato ( e), quindi salva le modifiche risultanti sul disco ed esce. Le &>/dev/nullimpedisce la GUI da brevemente lampeggiante sullo schermo, che può essere fastidioso.

Mi piace usare via volte per regex complicati super, perché (1) Perl è morto morire, (2) Vim ha un molto motore regex avanzate, e (3) Sono già intimamente familiare con vile regex nella mia giorno per giorno la modifica utilizzo documenti.


0
echo "/home/one/two/three/myfile.txt" | sed 's|\(.*\)/.*|\1|'

non preoccuparti, l'ho preso su un altro forum :)


4
quindi ottieni una partita avida: /home/one/two/three/se ne aggiungi un'altra /come /home/one/two/three/four/myfile.txtavresti anche una partita avida four: /home/one/two/three/fourla domanda è non avida
stefanB,

0

sed 's|\(http:\/\/www\.[a-z.0-9]*\/\).*|\1| funziona anche


0

Ecco qualcosa che puoi fare con un approccio in due passaggi e awk:

A=http://www.suepearson.co.uk/product/174/71/3816/  
echo $A|awk '  
{  
  var=gensub(///,"||",3,$0) ;  
  sub(/\|\|.*/,"",var);  
  print var  
}'  

Uscita: http://www.suepearson.co.uk

Spero che aiuti!


0

Un'altra versione di sed:

sed 's|/[:alnum:].*||' file.txt

Corrisponde /seguito da un carattere alfanumerico (quindi non da un'altra barra) e dal resto dei caratteri fino alla fine della riga. Successivamente lo sostituisce con nulla (cioè lo elimina.)


1
Immagino che dovrebbe essere "[[:alnum:]]", no "[:alphanum:]".
oli_arborum,
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.