11

I was splitting the output from id to provide a more readable line-by-list list of groups of which a user is a member:

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

I wanted to separate the group number from its bracketed name so I extended the expression like this

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

However, this did not act as I initially expected. The second expression appeared to have no effect.

Instead, to get the result I wanted, I needed to run two separate sed commands, like this:

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)

Why do I need two sed commands in a pipe rather than one with multiple instructions? Or if I can do this with one sed, how would I do so?

What I would particularly like is to have the single space between the UID/GID value and its bracketed name for every single item (including the UID and GIDs on the first line), but the caveat is that in my real data I can have groups containing brackets in their names and I don't want the names themselves mangled.

14

sed, like awk or cut or perl -ne works on each line individually one after the other.

sed -e code1 -e code2

is actually run as:

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

If your code2 is 2,$ s/foo/bar/, that's:

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

As your input has only one line, the sub() will never be run.

Inserting newline characters in the pattern space in code1 doesn't make the linenumber increase.

Instead, you have one pattern space with several lines in it while processing the first and only line of input. If you want to do modifications on the second line and over of that multi-line pattern space, you need to do something like:

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

Though here of course, you might as well do the two operations in one go:

id | sed 's/,\([^(]*\)(/\n\t\1 (/g'
  • awk, and perl -n/p, works on each record which defaults to a line but can be changed; in this case -vRS=, or -054 could help. – dave_thompson_085 Aug 24 '17 at 18:20
5

If you have GNU sed, you could use

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

which adds a space before the 4th and subsequent open parentheses, then replaces the commas.

  • 1
    That looks interesting. Unfortunately it also affects groupnames that contain brackets such as my example, international (uk) location, by inserting an unwanted space in the name itself. – roaima Aug 24 '17 at 17:22
  • Then use s/\([[:digit:]]\+\)(/\1 (/4g which will only add a space if there are digits before the parenthesis. – glenn jackman Aug 24 '17 at 17:27
1

What @stéphane-chazelas said is true, but you can always add the space first and split into lines after like this:

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

Or in a single sed script (without -e):

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

We normally use "/" as separator of command search (s), but it accepts any character as well, so sometimes it's easier to read using other char like ":" to avoid combinations like "/\".

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.