ext não são sempre capazes de corresponder apenas a uma linha

6

Considere o seguinte intervalo: 1,/pattern/ . Se o padrão corresponder na primeira linha, o intervalo corresponderá ao arquivo inteiro:

$ cat 1.sh
#!/usr/bin/env bash
set -eu
seq 1 4 | sed -rn '1,/'"$1"'/p'
$ ./1.sh 1
1
2
3
4
$ ./1.sh 2
1
2

O que você faria sobre isso?

UPD Veja o que eu fiz (apenas no caso):

re='/1/'
seq 1 4 | sed -rn "1{$re{p;q}}; 1,${re}p"

Ou desta forma:

seq 1 4 | sed -rn "1{/1/{p;q}}; 1,//p"
    
por x-yuri 04.08.2014 / 11:09

4 respostas

6

Sim, isso é uma coisa chata sobre sed (veja o sed FAQ sobre isso ). Já que você está usando o GNU sed ( -r é específico do GNU), você pode fazer:

 sed -En "0,/$1/p"

(Eu prefiro -E over -r como também é suportado por outros sed s como o FreeBSDs e é consistente com grep e algumas outras ferramentas (e vai estar no next issue dos padrões POSIX / Single UNIX Specification ).

Uma alternativa melhor (e portátil) seria:

sed "/$1/q"

Para dizer a sed para sair (e parar de ler) após a primeira partida.

Observe que awk não tem o problema, por isso você pode escrever:

PATTERN=$1 awk 'NR==1, $0 ~ ENVIRON["PATTERN"]'

(embora goste de sed , você prefere escrever):

PATTERN=$1 awk '1; $0 ~ ENVIRON["PATTERN"] {exit}'
    
por 04.08.2014 / 11:28
4

É um comportamento normal de sed . De POSIX sed documentação:

Addresses in sed

An address is either a decimal number that counts input lines cumulatively across files, a '$' character that addresses the last line of input, or a context address (which consists of a BRE, as described in Regular Expressions in sed , preceded and followed by a delimiter, usually a slash).

An editing command with no addresses shall select every pattern space.

An editing command with one address shall select each pattern space that matches the address.

An editing command with two addresses shall select the inclusive range from the first pattern space that matches the first address through the next pattern space that matches the second. (If the second address is a number less than or equal to the line number first selected, only one line shall be selected.) Starting at the first line following the selected range, sed shall look again for the first address. Thereafter, the process shall be repeated. Omitting either or both of the address components in the following form produces undefined results:

[address[,address]]

Você pode ver que sed imprimirá o intervalo inclusivo do primeiro endereço até o próximo endereço correspondente.

No seu caso, 1,/1/p , sed imprime a primeira linha porque corresponde ao endereço 1 . Em seguida, a partir da segunda linha, sed procurará o segundo endereço que corresponde ao padrão /1/ . E pare de imprimir, se encontrado. Como na segunda linha, você não tem nenhum padrão que corresponda a /1/ , por isso sed imprime o restante.

No caso de 1./2/p , sed imprime a primeira linha como acima, depois o segundo padrão de correspondência de linha /2/ , sed imprime e repete a ação para o resto. Mas você não pode combinar o endereço 1 para o resto, então sed não imprime nada.

Um exemplo:

$ echo 1 2 3 1 4 1 | tr ' ' $'\n' | sed -rn '1,/1/p'
1
2
3
1

Como você usa GNU sed , é possível usar o formulário 0,addr2 :

0,addr2
              Start  out  in  "matched  first  address"  state, until addr2 is
              found.  This is similar to 1,addr2, except that if addr2 matches
              the very first line of input the 0,addr2 form will be at the end
              of its range, whereas the 1,addr2 form  will  still  be  at  the
              beginning of its range.  This works only when addr2 is a regular
              expression.

Então, seu comando se torna:

seq 1 4 | tr ' ' $'\n' | sed -rn '0,/'"$1"'/p'

Então:

$ ./1.sh 1
1
    
por 04.08.2014 / 11:30
3

Existem várias coisas que você pode fazer. Por exemplo, o seu comentário indica que você pretende:

... exclui tudo, desde o início do arquivo até alguma linha específica, e essa linha é a primeira ...

Você pode fazer isso como:

sed -n "/$1"'/,$p'

Você apenas inverte o formulário. O comando acima só imprimirá de uma determinada linha até o final do arquivo.

Se você não quiser imprimir essa linha em particular ...

sed -n "/$1"'/,$p' | sed 1d

... deve fazer o truque ...

Além disso, você poderia apenas abordar a linha e fazer o ciclo em suas próprias mãos.

seq 20 | sed -ne"/$1"'/!d;:B' -e'n;p;bB'
seq 20 | sed -n "/$1"'/!d;h;n;G;P;D'

Ambos os comandos d eletem todas as linhas recebidas até encontrarem um padrão $1 .

O primeiro substitui o padrão de espaço com a linha de entrada n ext e configura um rótulo :b . Ele então p rints a linha e substitui o espaço padrão novamente com a n ext-line, antes de b ranching voltar para o rótulo :b . Ele faz um loop dessa maneira até o final do arquivo. Este comando é provavelmente mais rápido que o segundo - faz menos.

O segundo substitui h old-space pela correspondência $1 . Em seguida, ele também substitui o padrão de espaço com a linha n ext na entrada. Em seguida, G ets mantém espaço e anexa a linha de entrada que acabou de inserir - invertendo assim a ordem das duas linhas e delimitando entre elas com um caractere de nova linha. Algo como:

  • linha 1 > segure espaço

  • linha 2 > linha 1

  • reter espaço > > linha 2

  • = linha 2 \ n linha 1

Neste ponto, sed P rima apenas até o primeiro caractere \n ewline que ocorre no espaço padrão e D eletes mesmo antes de reiniciar o ciclo com o restante - que acaba sempre sendo a linha que correspondeu pela primeira vez $1 para todas as linhas . Portanto, a primeira linha correspondente a $1 é sempre no espaço padrão, mas nunca impressa.

E assim, se $1 for 5 , o seguinte será impresso:

6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
    
por 04.08.2014 / 11:33
0

Você pode manter o intervalo sed e fazer sem a opção -E ou -r quando usar uma lista de funções após o intervalo sed que contém o comando quit q (testado com FreeBSD & GNU sed ).

printf '%s\n' {1..10} | sed -n '1,/1/{p;q;}'

# your solution adapted to work with FreeBSD sed as well
re='/1/'
printf '%s\n' {1..4} | sed -En "1{$re{p;q;};}; 1,${re}p"
printf '%s\n' {1..4} | sed -En "1{/1/{p;q;};}; 1,//p"
    
por 29.10.2014 / 17:38

Tags