6

This question already has an answer here:

I've got text with whitespace (including a newline) that I need to replace. I'd like to use sed, but AIX doesn't seem to support the answers to sed newline questions I found. The version of bash I'm using is "GNU bash, version 3.00.16(1)-release (powerpc-ibm-aix5.1)"

If I run a command that outputs a bunch of pairs of lines of text like:

alias:  aliasname
                10:00:00:00:00:00:00:00

What is the best way to make it one line for each? If I use ssh user@system command | tr '\n' ' ', it replaces both newlines, and I need to keep the last one or else the output goes into a single line.

Edit: what I've tried so far is:

  • | sed -e ':1' -e 'N' -e '$!b1' -e 's/\n/ /g' which replaces all the newlines, putting the entire output stream into a single line.
  • | while read i; do printf "%s " $i; done; echo which also replaces all the newlines
  • | tr -d '\n' and | tr -d '\n'; echo, which still replace all newlines, however the echo adds one back at the last command.

marked as duplicate by don_crissti, DopeGhoti, Jeff Schaller, αғsнιη, DarkHeart Mar 10 '18 at 12:34

This question has been asked before and already has an answer. If those answers do not fully address your question, please ask a new question.

  • Is there any sort of pattern to what appears on the lines? Like, is the first line always alias: aliasname or something? – evilsoup Oct 31 '14 at 18:41
4

Here's a pure bash approach that will delete the first newline of every pair:

command | 
  while IFS=$'\n' read i; do 
    let c++; [ "$(expr $c % 2)" -eq "0" ] && echo "$i" || printf "%s " "$i"; 
  done

If you don't need to keep the whitespace unchanged, you can leave out the IFS:

command | 
  while read i; do 
    let c++; [ "$(expr $c % 2)" -eq "0" ] && echo "$i" || printf "%s " "$i"; 
  done

The same idea using perl:

command | perl -ne 'chomp;$. % 2 == 0 ? print "$_\n" : print "$_"' 
  • Thanks, like you said in chat: ugly, but functional and portable ;) – Basil Oct 31 '14 at 17:30
  • (1) IFS="\n" doesn’t work.  (Perhaps you meant IFS=$'\n'? But why not just IFS=?)  IFS="\n" seems to be equivalent to IFS=n.  If a line of input contains exactly one n, and it is the last character on the line, it will be stripped.  (Try your name, for example.)  (2) Your script fails in the pathological case that the last line of the input doesn’t end with a newline.  (3) Seriously?  You’re using expr?  In a loop? – G-Man Mar 10 '18 at 4:57
  • @G-Man (1) you're absolutely right, no idea what I was thinking. It does actually produce the expected output with the OP's example, which is why I didn't realize at the time. I wanted \n to leave other whitespace unchanged. (2) yes, but this shouldn't be done in the shell in the first place. I was going through a shell phase at the time and was interested in shell-only solutions. Silly though. (3) Why not? it's slow and inefficient, yes (see (2)), but if you're going for slow and inefficient (shell) anyway, why not? – terdon Mar 10 '18 at 14:30
5

Easyest

ssh user@system command | tr '\n' ' ';echo

Or if you want sed

ssh user@system command | sed 'N;s/\n\s\+/ /'
  • I tried this and it didn't replace the newline. – Basil Oct 30 '14 at 15:57
  • @Basil Show the exact line by ssh user@system command | cat -A - – Costas Oct 30 '14 at 16:17
  • cat: Not a recognized flag: A Version of bash is: GNU bash, version 3.00.16(1)-release (powerpc-ibm-aix5.1) – Basil Oct 30 '14 at 18:32
  • @Basil OK, you haven't GNU utilites. What you can receive on ssh user@system command | hd – Costas Oct 30 '14 at 18:44
  • 1
    Thanks guys! This is a really useful tool I learned about :) The output of an od -a shows me the characters ht lf ht ht between the lines I need to replace. The output of a straight od is 005011 004465 030072 030066. – Basil Oct 31 '14 at 16:29
4

Try this:

sed -e ':1' -e 'N' -e '$!b1' -e 's/\n/ /g'
  • I tried this and got sed: 0602-417 The label :1;N;$!b1;s/\n/ /g is greater than eight characters. – Basil Oct 30 '14 at 20:37
  • @Basil GNU sed allows ; in more places than AIX sed. Use a newline or separate -e arguments (see my edit). – Gilles Oct 30 '14 at 23:38
  • I tried it with the new version and instead of an error message, I got all my text on one line again, as if I had done a tr '\n' ' ' – Basil Oct 31 '14 at 16:30
0

It probably best to do this with sed; see Why is using a shell loop to process text considered bad practice?  For example, this minor variation of Costas’s second (sed) answer:

sed 'N; s/\n/ /'

does a pretty good job of answering the question: N to append the next line of input into the pattern space, and then s/\n/ / to do the substitution.  (And then these commands repeat for every two lines of input.)

But, if you want to do it entirely in the shell, this variation of terdon’s answer:

while IFS= read -r line1
do
    IFS= read -r line2; printf '%s %s\n' "$line1" "$line2"
done

works without running any external program(s).  The logic is (IMHO) simple: while not at the end of the input, read a line, and then read another line, and then output the two strings, separated by spaces, on one line.

The above code has a problem with an edge case: if the input is an even number of well-formed lines followed by some characters not terminated with a newline, it discards the final (partial) line.  (terdon’s answer has the same issue, but not just for even numbers of lines.)  This is because read returns failure when it reads a partial line, even though it does put the data into the variable.  So the fix is this:

while IFS= read -r line1  ||  [ "$line1" != "" ]
do
    IFS= read -r line2; printf '%s %s\n' "$line1" "$line2"
done

which keeps going until read fails and sets the variable to an empty string.

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