Sed para imprimir apenas a primeira correspondência de padrões da linha

5

Eu tenho alguns dados como

<td><a href="data1">abc</a> ... <a href="data2">abc</a> ... <a href="data3">abc</a>

(Referir-se-ia à linha acima como data no código abaixo)

Eu preciso de data1 entre o primeiro " e " , então eu faço

echo 'data' | sed 's/.*"\(.*\)".*//'

mas retorna-me a última string entre " e " always, ou seja, neste caso, ele retornaria data3 em vez de data1

Para obter data1 , acabo fazendo

echo 'data' | sed 's/.*"\(.*\)".*".*".*".*".*//'

Como obtenho data1 sem essa redundância em sed

    
por GypsyCosmonaut 01.08.2017 / 15:17

5 respostas

10

O .* no padrão regex é ganancioso, ele corresponde a uma string o maior tempo possível, então as citações correspondentes serão as últimas.

Como o separador é apenas um caractere aqui, podemos usar um grupo de colchetes invertidos para corresponder a qualquer coisa, exceto uma citação, ou seja, [^"] e, em seguida, repetições para corresponder a um número de caracteres que não sejam aspas. p>

$ echo '... "foo" ... "bar" ...' | sed 's/[^"]*"\([^"]*\)".*//'
foo

Outra forma seria apenas remover tudo até a primeira cotação e, em seguida, remover tudo a partir da (nova) primeira citação:

$ echo '... "foo" ... "bar" ...' | sed 's/^[^"]*"//; s/".*$//'
foo

Em regexes Perl, os especificadores * e + podem ser tornados não-ávidos ao anexar um ponto de interrogação, portanto .*? seria qualquer coisa, mas o mínimo de caracteres / bytes possível.

    
por 01.08.2017 / 15:23
4

Eu não vou aborrecer você com o aviso clássico contra o uso de expressões regulares simples para analisar HTML. Basta dizer que você deve usar um analisador dedicado em seu lugar. Dito isso, a questão aqui é que sed usa correspondência gulosa. Por isso, sempre corresponderá à string mais longa possível. Isso significa que seu .* continua para sempre e corresponde à linha inteira.

Você poderia fazer isso em sed (veja abaixo), mas usar uma ferramenta que permita correspondências não-gananciosas seria mais simples:

$ perl -pe 's/.*?"(.*?)".*/$1/' file
data1

Como sed não suporta correspondências não gananciosas, você precisa de algum outro truque. O mais simples seria usar a abordagem "não aspas" na resposta do ikkachu . Aqui está uma alternativa:

$ rev file | sed 's/.*"\(.*\)".*//' | rev
data1

Isso apenas inverte o arquivo ( rev ), usa sua abordagem original, que agora funciona, já que a primeira ocorrência é agora a última e, em seguida, inverte o arquivo novamente.

    
por 01.08.2017 / 15:25
4

Aqui estão algumas maneiras de extrair dados1 da sua entrada:

grep -oP '^[^"]*"\K[^"]*'

sed -ne '
   /\n/!{y/"/\n/;D;}
   P
'

perl -lne '/"([^"]*)"/ and print($1),last'
    
por 01.08.2017 / 16:24
3

Enquanto a pergunta não está marcada com awk , mas porque não usá-la como é simplesmente:

awk -F\" '{print $2}' infile.txt 
    
por 01.08.2017 / 16:30
2

Você também pode usar uma pesquisa não gulosa usando a expressão regular perl e olhar para trás:

cat data | grep -Po '(?<=href=").*?(?=")' | head -n1
    
por 01.08.2017 / 15:31

Tags