Por que o `sed expr1 | sed expr2` diferente de `sed -e expr1 -e expr2`

10

Eu estava dividindo a saída idpara fornecer uma lista mais legível de linha por lista de grupos dos quais um usuário é membro:

id roaima | sed 's/,/\n\t/g'
uid=1001(roaima) gid=1001(roaima) groups=1001(roaima)
    24(cdrom)
    25(floppy)
    ...
    822413650 (international (uk) location)

Eu queria separar o número do grupo do nome entre colchetes, então estendi a expressão assim

id roaima | sed -e 's/,/\n\t/g' -e '2,$s/(/ (/'

No entanto, isso não funcionou como eu esperava inicialmente. A segunda expressão parecia não ter efeito.

Em vez disso, para obter o resultado desejado, eu precisava executar dois sedcomandos separados , como este:

id roaima | sed -e 's/,/\n\t/g' | sed '2,$s/(/ (/'
uid=1001(roaima) gid=1001(roaima) groups=1001(roaima)
    24 (cdrom)
    25 (floppy)
    ...
    822413650 (international (uk) location)

Por que preciso de dois sedcomandos em um pipe, em vez de um com várias instruções? Ou se eu posso fazer isso com um sed, como eu faria?

O que eu particularmente gostaria de ter é o espaço único entre o valor UID / GID e seu nome entre colchetes para cada item (incluindo o UID e GIDs na primeira linha), mas a ressalva é que, em meus dados reais, posso ter grupos contendo colchetes em seus nomes e eu não quero que os nomes sejam mutilados.

roaima
fonte

Respostas:

14

sed, goste awkou cutou perl -netrabalhe em cada linha individualmente, um após o outro.

sed -e code1 -e code2

é realmente executado como:

while(patternspace = getline()) {
  linenumber++
  code1
  code2
} continue {print patternspace}

Se o seu código2 for 2,$ s/foo/bar/, é isso:

if (linenumber >= 2) sub(/foo/, "bar", patternspace)

Como sua entrada possui apenas uma linha, a sub()nunca será executada.

Inserir caracteres de nova linha no espaço padrão code1não linenumberaumenta.

Em vez disso, você tem um espaço de padrão com várias linhas enquanto processa a primeira e única linha de entrada. Se você deseja fazer modificações na segunda linha e acima desse espaço padrão de várias linhas, é necessário fazer algo como:

s/\(\n[^(]*\)(/\1 (/g

Embora aqui, é claro, você também pode executar as duas operações de uma só vez:

id | sed 's/,\([^(]*\)(/\n\t\1 (/g'
Stéphane Chazelas
fonte
awk, e perl -n / p, funciona em cada registro cujo padrão é uma linha, mas que pode ser alterado; neste caso -vRS=,ou -054poderia ajudar.
Dave_thompson_085
5

Se você possui o GNU sed, você pode usar

id username | sed 's/(/ (/4g; s/,/\n\t/g'

que adiciona um espaço antes do quarto e parênteses abertos subsequentes e substitui as vírgulas.

Glenn Jackman
fonte
1
Isso parece interessante. Infelizmente, isso também afeta nomes de grupos que contêm colchetes, como meu exemplo international (uk) location, ao inserir um espaço indesejado no próprio nome.
roaima
Em seguida, use s/\([[:digit:]]\+\)(/\1 (/4gapenas um espaço se houver dígitos antes do parêntese.
Glenn Jackman
1

O que @ stéphane-chazelas disse é verdade, mas você sempre pode adicionar o espaço primeiro e dividir em linhas depois desta forma:

sed -e 's:\([,=][0-9]*\):\1 :g' -e 's:,:\n\t:g'

Ou em um único script sed (sem -e):

sed 's:\([,=][0-9]*\):\1 :g; s:,:\n\t:g'

Normalmente, usamos " /" como separador da (s) busca (s) de comando, mas também aceita qualquer caractere; portanto, às vezes é mais fácil ler usando outros caracteres como " :" para evitar combinações como " /\".

WPomier
fonte