Pourquoi `sed expr1 | sed expr2` différent de `sed -e expr1 -e expr2`

10

Je divisais la sortie de idpour fournir une liste ligne par liste plus lisible des groupes dont un utilisateur est membre:

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

Je voulais séparer le numéro de groupe de son nom entre crochets, j'ai donc étendu l'expression comme ceci

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

Cependant, cela n'a pas fonctionné comme je m'y attendais initialement. La deuxième expression semble n'avoir aucun effet.

Au lieu de cela, pour obtenir le résultat souhaité, je devais exécuter deux sedcommandes distinctes , comme ceci:

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)

Pourquoi ai-je besoin de deux sedcommandes dans un canal plutôt que d'une avec plusieurs instructions? Ou si je peux le faire avec un sed, comment pourrais-je le faire?

Ce que j'aimerais particulièrement, c'est avoir l'espace unique entre la valeur UID / GID et son nom entre crochets pour chaque élément (y compris l'UID et les GID sur la première ligne), mais la mise en garde est que dans mes données réelles, je peux avoir des groupes contenant des parenthèses dans leurs noms et je ne veux pas que les noms eux-mêmes soient mutilés.

roaima
la source

Réponses:

14

sed, comme awkou cutou perl -netravaille sur chaque ligne individuellement les unes après les autres.

sed -e code1 -e code2

est en fait exécuté comme:

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

Si votre code2 est 2,$ s/foo/bar/, c'est:

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

Comme votre entrée n'a qu'une seule ligne, le sub()ne sera jamais exécuté.

L'insertion de caractères de nouvelle ligne dans l'espace de motif dans code1n'augmente pas linenumber.

Au lieu de cela, vous disposez d'un espace de motif contenant plusieurs lignes pendant le traitement de la première et seule ligne d'entrée. Si vous souhaitez effectuer des modifications sur la deuxième ligne et plus de cet espace de modèle multiligne, vous devez faire quelque chose comme:

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

Bien qu'ici bien sûr, vous pourriez aussi bien faire les deux opérations en une seule fois:

id | sed 's/,\([^(]*\)(/\n\t\1 (/g'
Stéphane Chazelas
la source
awk et perl -n / p, fonctionne sur chaque enregistrement qui par défaut est une ligne mais peut être modifié; dans ce cas -vRS=,ou -054pourrait aider.
dave_thompson_085
5

Si vous avez GNU sed, vous pouvez utiliser

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

qui ajoute un espace avant la 4e et les parenthèses ouvertes suivantes, puis remplace les virgules.

glenn jackman
la source
1
Ça a l'air intéressant. Malheureusement, cela affecte également les noms de groupe qui contiennent des crochets comme mon exemple international (uk) location, en insérant un espace indésirable dans le nom lui-même.
roaima
Utilisez ensuite s/\([[:digit:]]\+\)(/\1 (/4gqui ajoutera un espace uniquement s'il y a des chiffres avant la parenthèse.
glenn jackman
1

Ce que @ stéphane-chazelas a dit est vrai, mais vous pouvez toujours ajouter l'espace en premier et le diviser en lignes après comme ceci:

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

Ou dans un seul script sed (sans -e):

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

Nous utilisons normalement " /" comme séparateur des recherches de commande, mais il accepte également n'importe quel caractère, donc il est parfois plus facile de lire en utilisant d'autres caractères comme " :" pour éviter les combinaisons comme " /\".

WPomier
la source