4

File.txt

String1?
word1
word2
word3
word4

String2?
word5
word6
word7
word8

Desired Output:

String1?
word1 | word2
word3 | word4

String2?
word5 | word6
word7 | word8

Only pattern is Lines with /?$/ I tried:

sed '/\?$/{n;:l N;/\?$/b; :a; N; $!b a; s/\n\s\{1,\}/ | /g; bl}'

but it didn't work. My current working solution:

sed '/\?$/{:a;N;/\n....-..-.. /!s/\n/ - /;ta;P;D}' | sed 's/^[- ]*//g;s/[ -]*$//g'

... but it's a work-around and is extremely slow. Can anyone help with a single one liner without pipes and is a fast solution?

If an empty line doesn't exist, as in pattern ,$\|^$, and if ^$ is not there and is having another line with ?$, then how can we hold the buffer from ?$ to first non-greedy ?$ pattern and then print all lines except the last line and merge the last line with the next pattern buffer for search?

1
  • The escaped question mark /\?/ or plus /\+/ is a special character - a quantifier.
    – nezabudka
    Jan 7 at 5:14
4

GNU sed only.
If all lines of the block have 2 columns exactly (your case):

sed '/?$\|^$/b;N;s/\n/ | /' File.txt

If odd-numbered content is possible (universal way):

sed '/?$\|^$/b;N;/\n$/!s/\n/ | /' File.txt
8
  • 4
    Note that it assumes GNU sed. There's no alternation operator in POSIX BRE, you can't have any other command after the b one on the same line. POSIXly: sed -e '/^\(.*?\)\{0,1\}$/b' -e '$!N;s/\n/ | /' Jan 7 at 6:16
  • @StéphaneChazelas There exists other sed implementations that allows b with no label occurring before other commands in a single -e expression.
    – they
    Jan 7 at 7:45
  • @they, yes, though that would be the ones that were designed to mimic the GNU implementation like busybox. It's true these days, a lot of the GNUism have permeated into other implementations. Note that it's not only for b with no label. b a;b branches to the a;b label in historical implementations. That even used to be required by POSIX (I was the one asking for that requirement to be relaxed). Jan 7 at 7:57
  • @StéphaneChazelas, thanks. Can you check this? sed -e '/?$\|^$/b' -e 'N;/\n$/!s/\n/ | /' The option you suggested does not work with odd content.
    – nezabudka
    Jan 7 at 14:31
  • 2
    The solution I gave above was a POSIX translation of your first solution. There's no \| in POSIX sed. \{0,1\} is the POSIX BRE equivalent of POSIX ERE ? (or GNU BRE \?). In POSIX sed (including GNU sed when POSIXLY_CORRECT is in the environment), calling N on the last line of input discard the current pattern space. Jan 7 at 14:35
4

Assuming your input is blocks of text separated by blank lines as shown in your sample input, then using any awk in any shell on every Unix box:

$ awk -v RS= -F'\n' -v OFS=' | ' '{print $1; for (i=2; i<NF; i+=2) print $i, $(i+1); print ""}' file
String1?
word1 | word2
word3 | word4

String2?
word5 | word6
word7 | word8
4
$ sed '/?$/,/^$/ { //b; N; y/\n/|/; }' file
String1?
word1|word2
word3|word4

String2?
word5|word6
word7|word8

For lines in the range /?$/,/^$/, i.e., from a line with a ? at the end to an empty line:

  • If the current line is either the first or last in the range, nothing is done (//b; "branch to the end of the script if the most recently matched regular expression (in this cycle) matches").
  • Otherwise, append the next line of input to the buffer (N), and replace the inserted newline character with a | character. If you want spacing around the pipe, use s/\n/ | / instead of y/\n/|/.

POSIX-ly:

sed -e '/?$/,/^$/ { //b' -e N -e 'y/\n/|/' -e '}' file

or, with a separate script file, here provided via a here-document,

sed -f /dev/stdin file <<'END_SED'
/?$/,/^$/ {
    // b
    N
    y/\n/|/
}
END_SED

The same sort of thing with awk,

awk -v OFS='|' '
    /\?$/,length == 0 {
        if ( !/\?$/ && length != 0) {
            getline n
            print $0, n
        } else print
    }' file

The above code could also have used /^$/ in place of length == 0, and where you could use OFS=' | ' in place of OFS='|' if you want spaces around the pipe delimiters in the output.

0

Using awk we may try the following:

awk -v OFS=' | ' '
!NF ||  /\?$/ {k=0}
NF  && !/\?$/ {k++}
{ORS = k%2 ? OFS : RS}1
' file

Output:-

String1?
word1 | word2
word3 | word4

String2?
word5 | word6
word7 | word8
0

Perl in paragraph mode (-00) we can do as follows:-

perl -aF'\n' -nls -00e '
  print shift @F;
  print splice(@F,0,2) while @F>1;
  print @F if !eof;
' -- -,=" | " file

Using GNU sed in its extended regex mode (-E) to simplify regex writing:

sed -E '
  /./{H;$!d;}
  x;$!G;s///
  s/([^\n]*\n){2}/&|/g
  s/\n\|/ | /g
' file
-4
    cat ll.txt
    
    String1?
    word1
    word2
    word3
    word4
    
    String2?
    word5
    word6
    word7
    word8
       

 awk '/String/' ll.txt  >lp.txt    
    
    co=$(awk '/String/' ll.txt |wc -l)
    
    for ((j=1;j<=$co;j++)); do fist=$(sed -n ''$j'p' lp.txt);lasg=$(($j+1)); lasp=$(sed -n ''$lasg'p' lp.txt );  echo -e "$fist\n";awk -v fist="$fist" -v lasp="$lasp" '$0 == fist{f=1}$0 == lasp{f=0;print}f' ll.txt|sed -e '1d' |sed '/String/d'|sed "N;s/\n/|/g";done



    output
    
    String1?
    
    word1|word2
    word3|word4
    
    String2?
    
    word5|word6
    word7|word8
1
  • what is the was a line like string1|string2 instead of word1|wod2, etc? then all your long command won't help and skipping what that long command does in the middle but it produced a wrong output; ""please do not process a text file using shell-loop"" yesterday

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.