4

Pseudocode which is a continuation from this answer

gsed 's/|/1)/g' 's/|/2)/2g' 's/|/3)/3g' 's/|/5)/4g' 's/|/5)/5g' 
<input.csv >output.csv

which is of course not working. I am interested in how gsed can manage such an looping.

How does gsed extend to such looping?

2 Answers 2

5

For the case above, you can do it like this:

gsed 's/|/1)/; s/|/2)/; s/|/3)/; s/|/4)/; s/|/5)/'

Example:

$ echo '| | | | |' | sed 's/|/1)/; s/|/2)/; s/|/3)/; s/|/4)/; s/|/5)/'
1) 2) 3) 4) 5)

This works if you can estimate in advance the maximum number of | on a line, and add s/|/N)/ accordingly.

If you can't estimate the maximum number of | on a line it can still be done with gsed, using a counter in the hold buffer, and incrementing it with this clever device by Bruno Haible. The actual implementation is a little tricky though, and thus I'll leave it to the masochisticastute reader.

The easy way is, of course, to just use awk:

awk '{ cnt = 0; while(sub(/\|/, ++cnt ")")); print }'

Proof:

$ echo '| | | | |' | awk '{ cnt = 0; while(sub(/\|/, ++cnt ")")); print }'
1) 2) 3) 4) 5)
5
  • Excellent link for the arithmetic with gsed! What is this tn command? Not in my OSX brew. Jun 11, 2015 at 20:11
  • 1
    tn is actually t n, jump to label n if a s/// has done a successful substitution since the last t. It's a standard option, and it's supported in in *BSD sed.
    – lcd047
    Jun 11, 2015 at 20:15
  • Interesting! I opened a new thread about Sed's arithmetic here unix.stackexchange.com/q/209102/16920 I want to do look-behind by Sed. Jun 11, 2015 at 20:22
  • 1
    @Masi - it is in your OSX sed - BSD and GNU sed both - and any other POSIX sed for that matter - will handle a test/s///ubstitute loop. It's just a simple s///;if true:jump statement. But the return tested will be true if any one of any number of s///ubstitutions occurs since the last line was pulled into pattern space, or since the last :label was defined, or since the last test was attempted. Without a param - (like n) test will branch a successfully s///ubstituted line completely out of the script - so it can be used for conditional input pruning, too.
    – mikeserv
    Jun 11, 2015 at 20:26
  • 1
    @Masi - doing math w/ regexp will only bring you frustration - take it from one who has tried. Rather use sed/other regexp solutions as macro preprocessors for some efficient calculator/compiler solution. I recommend dc.
    – mikeserv
    Jun 11, 2015 at 20:32
3

Another sed:

sed -n '1x;1s/^/12345/;1x;G;:t
        s/|\(.*\n)\(.\)/\2)\1/;tt
P'    <infile >outfile

But probably awk is more suitable here.

A much more complicated solution:

Given the output taken from your other question...

Question ipsun; option 1 | option 2 | option 3 | option 4 | ... | option n
Que|stion ipsun; option 1 | option 2 | option 3 | option 4 | ... | option n

...and passing that to...

sed '   s/^/!/;/|/s/\(.*;\)*[^|]*/&\
|/g;    s/.*\(\n\).*/::::::\1&\1::/' |
nl  -d:: -hp^\) -s\)\              |
sed -e' /./{ s/^ *!//;P;d;:n' -e'};N;/\n$/!bn
        s/[0-9 ]*\n *\([^;|)]*) |*\)|/ (\1/g;D'

...to get...

Question ipsun; option (1) | option (2) | option (3) | option (4) | ... (5) | option n (6)
Que|stion ipsun; option (1) | option (2) | option (3) | option (4) | ... (5) | option n (6)

I guess I'll break it down...

sed '   s/^/!/;/|/s/\(.*;\)*[^|]*/&\
)/g;    s/.*\(\n\).*/::::::\1&\1::/' |..

.

... is the first stage. This preps output for nl.

nl has a unique feature among the standard utilities - (the majority of which treat an entire input stream as a single, cohesive unit) - in that it can logically separate an input file by section. nl will divide input into a header, body, and footer for each logical page of it. For the three sections per page a user might specify different number styles for each, and for each logical page the user can receive a new line count.

With sed above I do:

  • s/^/!/
    • If nothing else, this ensures that nl's section delimiter cannot occur in input accidentally, but this will also serve as a marker for trimming nl's leading blanks on the other side of its processing.
  • /|/s...
    • This only attempts to apply a substitution to a line which definitely matches at least one |pipe.
  • ...s/\(.*;\)*[^|]*/&\n|/g
    • This applies a global substitution against a pattern that can match anything (even nothing) but a |pipe which is not followed by at some point by a ;semicolon.
      • Because the \(.*;\)* is matched * zero-or-more times, it matches the null-string most of the time. But the first time the matcher applies the sub-expression on a line which contains at least one ; semicolon, then the entire range of pattern space from the head of the line through to the last semicolon on the line is effectively skipped.
      • The point of this is to avoid matching any [^|]* sub-strings that occur before the dividing semicolon, and thus to skip the Question portion of your string entirely.
    • For every occurrence of this pattern it is substituted for &itself, plus an appended \n|.
  • s/.*\(\n\).*/::::::\1&\1::/
    • This substitutes all of any pattern space which contains at least one \newline for six colons, then our captured \1\newline, then &itself, then our captured \1\newline again, then two colons.
    • I will be using the string :: as nl's section -delimiter. One of these strings begins an nl footer section, and three of these strings marks a header - and a new logical page.
    • nl will only recognize the delimiter if it occurs on a line all alone.

...and at this point the data stream looks like...

::::::
!Question ipsun; option 1
|| option 2
|| option 3
|| option 4
|| ...
|| option n
|
::
::::::
!Que|stion ipsun; option 1
|| option 2
|| option 3
|| option 4
|| ...
|| option n
|
::

...and now we can pass that through...

...|nl -d:: -hp^\) -s\)\ |...
  • -d::
    • This specifies the :: delimiter string.
    • nl will replace every -delimiter string in input with a blank line on output.
      • nl will either insert numbers at the head of its output lines, or it will insert spaces to match the length of its number string at the head of lines which it does not number.
      • And so the only lines in its output which are ever blank are those which used to be its delimiter strings.
  • -hp^\|
    • This is a -header style meaning number all lines which match the regex pattern.
    • nl will be in a header state for each sequence of lines occurring between one containing only :::::: and another containing only ::.
    • By default nl numbers only body sections and doesn't number headers or footers at all.
  • -s\)\
    • This is a separator string which nl will insert between the head of each line it numbers and the number it inserts before it.

...and at this point the data stream looks like...

        !Question ipsun; option 1
     1) || option 2
     2) || option 3
     3) || option 4
     4) || ...
     5) || option n
     6) |


        !Que|stion ipsun; option 1
     1) || option 2
     2) || option 3
     3) || option 4
     4) || ...
     5) || option n
     6) |

...and last we can handle that with...

sed -e' /./{s/^ *!//;P;d;:n' -e '};N;/\n$/!bn
        s/[0-9 ]*\n *\([^;|)]*) |*\)|/ (\1/g;D'
  • /./{...
    • On any line containing at least one character this begins a match-focused function.
    • Several commands may follow, but will only apply to pattern spaces which first matched the function's parent address.
  • ...{s/^ *!//
    • Here we trim all of nl's inserted blanks and our inserted bang from the head of matching lines.
  • P;d;:n
    • This Prints up to the first \newline in pattern space.
    • Then deletes pattern space.
    • Then defines the branch label :n.
    • From this point on we're only working with a sequence of lines which began with a blank one.
  • N
    • This pulls in the Next input line and appends it pattern space following a \newline character.
  • /\n$/!bn
    • While the pattern space does not $end with a \newline - as it will be if the Next line is blank - we branch back to the :n label - stacking input all the while.
  • s/[0-9 ]*\n *\([^;|)]*) |*\)|/ (\1/g'
    • This is a global substitution.
    • It replaces every sequence of...
      • *zero-or-more [0-9 ] chars
      • followed by a \newline
      • followed by *zero-or-more <spaces>
      • followed by *zero-or-more ^not colon or pipe chars
      • followed by a right )paren
      • possibly followed by a |pipe
      • definitely followed by a |pipe
      • with just the ^not colon/pipe chars, right paren, and possible pipe.
  • D
    • And now we Delete up to the first \newline in pattern space (which is the very first character, since this sequence began with a blank line) and send the rest back to the top of the script to get the spaces and bang trimmed and Printed in the next new cycle.

The end result is:

Question ipsun; option (1) | option (2) | option (3) | option (4) | ... (5) | option n (6)
Que|stion ipsun; option (1) | option (2) | option (3) | option (4) | ... (5) | option n (6)
2
  • Why is there this pipe "|" in this word "Que|stion"? Jun 12, 2015 at 12:54
  • 1
    @Masi - because I put it there while testing - to see if it could handle random characters. I also played around with many other variations.
    – mikeserv
    Jun 12, 2015 at 13:15

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .