é 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
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 ...
é 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
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 /^*/
.
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 <()
.
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.
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.
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
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$
Aqui é uma maneira com awk
:
awk '!/^\*/{a[++i]=$0;next}1 END{while(x<length(a))print a[++x]}' file
Tags text-processing sed sort