#abstract
Print lines without newline, add a newline only if there is another line to print.
$ printf 'one\ntwo\n' |
awk '{ printf( "%s%s" , NR>1?"\n":"" , $0 ) }'; echo " done"
one
two done
#Other solutions
If we were working with a file, we can just truncate one character from it (if it ends on a newline):
removeTrailNewline(){ [[ $(tail -c 1 "$1") ]] || truncate -s-1 "$1"; }
That is a fast solution as it needs to read only one character from the file and then remove it directly (truncate
) without reading the whole file.
However, while working with data from stdin (an stream) the data must be read, all of it.
And, it is "consumed" as soon as it is read. No backtrack (as with truncate).
To find the end of an stream we need to read to the end of the stream. At that point, there is no way to move back on the input stream, the data has been already "consumed". This means that data must be stored in some form of buffer until we match the end of the stream and then do something with the data in the buffer.
The most obvious of solutions is to convert the stream into a file and process that file. But the question asks for some kind of filter of the stream. Not about the use of additional files.
###variable
The naive solution would be to capture the whole input into a variable:
FilterOne(){ filecontents=$(cat; echo "x"); # capture the whole input
filecontents=${filecontents%x}; # Remove the "x" added above.
nl=$'\n'; # use a variable for newline.
printf '%s' "${filecontents%"$nl"}"; # Remove newline (if it exists).
}
printf 'one\ntwo' | FilterOne ; echo 1done
printf 'one\ntwo\n' | FilterOne ; echo 2done
printf 'one\ntwo\n\n' | FilterOne ; echo 3done
###memory
It is possible to load a whole file in memory with sed. In sed it is impossible to avoid the trailing newline on the last line. GNU sed might avoid printing a trailing newline, but only if the source file is already missing it. So, no, simple sed can't help.
Except on GNU awk with the -z
option:
sed -z 's/\(.*\)\n$/\1/'
With awk (any awk), slurp the whole stream, and printf
it without the trailing newline.
awk ' { content = content $0 RS }
END { gsub( "\n$", "", content ); printf( "%s", content ) }
'
Loading a whole file into memory might not be a good idea, it may consume a lot of memory.
###Two lines in memory
In awk, we can process two lines per loop by storing the previous line in a variable and printing the present one:
awk 'NR>1{print previous} {previous=$0} END {printf("%s",$0)}'
###Direct processing
But we could we do better.
If we print the present line without a newline and print a newline only when a next line exists, we process one line at a time and the last line will not have a trailing newline:
awk 'NR==1{ printf("%s",$0);next }; { printf( "\n%s", $0 ) }'
Or, written in some other way:
awk 'NR>1{ print "" }; { printf( "%s", $0 ) }'
Or:
awk '{ printf( "%s%s" , NR>1?"\n":"" , $0 ) }'
So:
$ printf 'one\ntwo\n' | awk '{ printf( "%s%s" , NR>1?"\n":"" , $0 ) }'; echo " done"
one
two done