Precisa de alguma mágica sed: Mover linhas marcadas para o início de um arquivo

5

Eu me vejo em extrema necessidade de alguma sed magic (eu realmente preciso me sentar e aprender isso). Eu tenho um arquivo com muitas linhas. Depois de analisá-lo, marquei algumas linhas adicionando uma estrela (*) ao início da linha.

O que eu gostaria de fazer com algum truque sed (se possível), é mover todas as linhas marcadas para o início (ou o final - eu não sou exigente) do arquivo, então elas formam um bloco único. As outras linhas (não marcadas) devem ser deixadas intactas.

Como posso fazer algo assim com sed ? Eu sei sed tem alguns buffers para mover o texto ...

    
por Baard Kopperud 28.06.2013 / 05:56

7 respostas

8

é sed essencial? se você não se importar com duas passagens pelo arquivo de origem, isso é feito facilmente com o grep.

por exemplo,

grep '^\*' input > outputfile
grep -v '^\*' input >> outputfile
    
por 28.06.2013 / 06:18
8

Dependendo do tamanho do arquivo, você pode fazer:

sed '/^*/!H;//p;$!d;g;s/\n//'

Isso empilha em H as linhas de espaço antigas que não correspondem a /^*/ . Aqueles que correspondem são p rinted quando ocorrem na entrada. Então, todas as linhas que são ! e não $ last são d da saída. Na última linha, g et ocupamos espaço sobrescrevendo o espaço padrão, então o primeiro caractere \n ewline é s/\n// substituído, porque a primeira linha H resultará em um extra a cada vez.

Isso requer um buffer grande, já que armazena todas essas linhas em H old space. Isso, por outro lado ...

sed '/^*/p;$!d;g;r file' <file    |
sed -e '1,/^$/{/./p;d' -e '};/^*/d'

... não tem esse requisito.

A primeira sed p apenas rima /^*/ linhas coincidentes até a $ última linha, quando ela imprime uma linha em branco, então r extrairá todo o arquivo de entrada novamente.

O segundo sed funciona primeiro em um intervalo de linha da primeira linha até a primeira linha em branco p rinting todas as linhas que correspondem a pelo menos um único caractere, em seguida d eleting the lot. Depois de encontrar a primeira linha em branco, então d elimina todas as linhas que correspondem a /^*/ .

    
por 12.12.2014 / 23:32
6

Você não precisa de sed para fazer isso, você pode usar alguns greps básicos para puxar as linhas star (*) para o topo. Digamos, por exemplo, que você tinha esse arquivo:

$ cat sample.txt 
1
2
3
4
* 5
* 6
* 7
8
9
10

Agora para grep o arquivo sample.txt colocando as linhas estrela (*) primeiro:

$ cat <(grep '*' sample.txt) <(grep -v '*' sample.txt)
* 5
* 6
* 7
1
2
3
4
8
9
10

O acima irá executar 2 greps, o 1º puxa todas as linhas com estrelas, enquanto o 2º puxa todas as linhas sem estrela. A saída desses dois comandos é redirecionada como entrada para o comando cat usando a notação <() .

Método alternativo

Se você não quiser usar cat + os 2 subshells que você pode fazer como foi sugerido por @terdon:

$ grep '*' sample.txt; grep -v '*' sample.txt

Isso irá retirar todas as linhas de sample.txt que incluem uma estrela (*) seguida por todas as linhas que não o fazem.

Referências

por 28.06.2013 / 06:45
3

Aqui está uma solução usando ed (e expandindo um pouco na solução vi postada por Kaz, que usa abaixo ex - um ed reforçado). Substitua w por ,p\n para imprimir a saída em vez de gravar no arquivo.

Mova as linhas correspondentes para o final do arquivo:

ed -s file <<< $'g/^\*/m$\nwq\n'

O subcomando g lobal marca todas as linhas que correspondem ao padrão. Então, para cada linha marcada, ela define a linha atual para a linha marcada e executa o subcomando : m ove após a linha no endereço $ (após a última linha); então w rite para o arquivo e q uit o editor.

Mova as linhas correspondentes para o início do arquivo (ou melhor, mova as linhas não correspondentes para o final do arquivo - mas é o resultado final que importa - mantenha a ordem da linha):

ed -s file <<< $'v/^\*/m$\nwq\n'

O subcomando global v erse marca cada linha que NÃO corresponde ao padrão. Então, para cada linha marcada, ela define a linha atual para a linha marcada e executa o subcomando : m ove após a linha no endereço $ (após a última linha); então w rite para o arquivo e q uit o editor.

O motivo pelo qual eu uso correspondência inversa para alcançar o resultado desejado, em vez de g/^\*/m0 (mover todas as linhas correspondentes para o início) sugerido por Kaz, é que (como ele apontou) quando você move as linhas correspondentes para o início vai acabar na ordem inversa.
Isso - como enfatizei acima - é porque g e v , para cada linha marcada, defina a linha atual para a linha marcada e execute o (s) subcomando (s) especificado (s) . A primeira linha marcada como correspondência é movida após a linha endereçada pelo parâmetro após m : 0 neste caso - portanto, ela se torna a primeira linha. Em seguida, a segunda linha correspondente é movida para o mesmo endereço, de modo que se torne a primeira linha e a linha que foi movida antes de se tornar a segunda linha ... e assim por diante ... até que a última linha correspondente seja movida para o endereço especificado.

    
por 13.12.2014 / 02:50
2

Se você não se importa com a linha vazia entre os dois blocos:

sed -n -e '/^* /{H;$!d}' -e '/^* /!p' -e '${g;p}'

ou o contrário

sed -n -e '/^* /{p;$!d}' -e '/^* /!H' -e '${g;p}' file    
    
por 28.06.2013 / 06:42
1

Como você adicionou os caracteres * manualmente, pode continuar trabalhando de maneira interativa. Em vi , você pode fazer isso:

:g/pattern/m<address>

para mover todas as linhas que correspondem a um padrão para o endereço fornecido. Se o endereço for dado como 0, ele moverá as linhas antes da primeira linha do arquivo. Então:

:g/^\*/m0

Note, no entanto, que as linhas marcadas aparecem em ordem inversa! Eu fiz esse tipo de coisa muitas vezes no passado. Para manter as linhas em ordem, o que faço é movê-las depois da última linha, em vez de antes da primeira linha, usando o endereço '$' em vez de '0':

:g/^\*/m$

Se eu os quiser na frente, então é um movimento manual fácil. Por exemplo, usando a seleção visual do Vim.

Na próxima vez, escolha um marcador que não seja um operador de regex que exija escape, embora neste caso o Vim aceite :g/^*/ . :)

É possível mover as linhas até o final e excluir o caractere de marcação em uma etapa. Isso funciona no Vim; O único problema é que não consigo encontrar a documentação que diz que é:

:g/^*/s/^.//|m$
    
por 28.10.2013 / 02:26
0

Aqui é uma maneira com awk :

awk '!/^\*/{a[++i]=$0;next}1 END{while(x<length(a))print a[++x]}' file
    
por 29.06.2013 / 17:24