¿Por qué es `sed expr1 | sed expr2` diferente a `sed -e expr1 -e expr2`

10

Estaba dividiendo la salida de idpara proporcionar una lista de grupos de línea por lista más legible de los cuales un usuario es miembro:

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

Quería separar el número de grupo de su nombre entre corchetes, así que extendí la expresión de esta manera

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

Sin embargo, esto no actuó como esperaba inicialmente. La segunda expresión parecía no tener efecto.

En cambio, para obtener el resultado que quería, necesitaba ejecutar dos 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 qué necesito dos sedcomandos en una tubería en lugar de uno con varias instrucciones? O si puedo hacer esto con uno sed, ¿cómo lo haría?

Lo que me gustaría particularmente es tener un espacio único entre el valor UID / GID y su nombre entre corchetes para cada elemento (incluidos el UID y GID en la primera línea), pero la advertencia es que en mis datos reales puedo tener grupos que contienen corchetes en sus nombres y no quiero que los nombres mismos sean destrozados.

roaima
fuente

Respuestas:

14

sed, Al igual que awko cuto perl -netrabajos sobre cada línea individualmente uno tras otro.

sed -e code1 -e code2

en realidad se ejecuta como:

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

Si su código2 es 2,$ s/foo/bar/, eso es:

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

Como su entrada tiene solo una línea, sub()nunca se ejecutará.

Insertar caracteres de nueva línea en el espacio del patrón code1no hace el linenumberaumento.

En cambio, tiene un espacio de patrón con varias líneas mientras procesa la primera y única línea de entrada. Si desea realizar modificaciones en la segunda línea y más de ese espacio de patrón de varias líneas, debe hacer algo como:

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

Aunque aquí, por supuesto, también podrías hacer las dos operaciones de una vez:

id | sed 's/,\([^(]*\)(/\n\t\1 (/g'
Stéphane Chazelas
fuente
awk, y perl -n / p, funciona en cada registro que por defecto es una línea pero se puede cambiar; en este caso -vRS=,o -054podria ayudar.
dave_thompson_085
5

Si tiene GNU sed, podría usar

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

que agrega un espacio antes del cuarto y paréntesis abiertos posteriores, luego reemplaza las comas.

Glenn Jackman
fuente
1
Eso se ve interesante. Desafortunadamente, también afecta a los nombres de grupos que contienen paréntesis, como mi ejemplo international (uk) location, al insertar un espacio no deseado en el nombre mismo.
roaima
Luego use s/\([[:digit:]]\+\)(/\1 (/4gque solo agregará un espacio si hay dígitos antes del paréntesis.
Glenn Jackman
1

Lo que dijo @ stéphane-chazelas es cierto, pero siempre puedes agregar el espacio primero y dividirlo en líneas después de esto:

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

O en un solo script sed (sin -e):

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

Normalmente usamos " /" como separador de búsqueda (s) de comandos, pero también acepta cualquier carácter, por lo que a veces es más fácil de leer usando otros caracteres como " :" para evitar combinaciones como " /\".

WPomier
fuente