sed
takes all the strings passed as arguments to -e
options and the contents of files passed to -f
options and concatenates them together with newlines in between to make up the sed
script to interpret.
perl
(without -f
) and GNU awk
do the same.
For instance doing:
sed -eA -f file1 -e 'C
D' -f file2 -e 'F;G'
Where file1
contains B
and file2
contains E
is the same as running:
sed -e 'A
B
C
D
E
F;G'
If not passed any -e
or -f
argument, the first non-option argument is taken as the literal sed
code to interpret, so it's also the same as:
sed 'A
B
C
D
E
F;G'
In the rudimentary sed
language (from the 70s; itself inspired from ed
from the 60s), commands can be separated with newlines (or with separate -e
s which is equivalent to adding newlines) and some of them can be separated with ;
.
There are some exceptions for those commands that take arguments and some others for historical but not good reason.
You can't separate a :
, or b
or t
command from the next with ;
because in the original sed
implementation and as still allowed by POSIX and still the case with many sed
implementations:
b foo;bar
b
ranches to the foo;bar
label. That's not b foo
followed by b ar
like it is in GNU sed
.
Same goes for r
or w
(or the w
flag of the s
command) with which even in GNU sed
r foo;bar
is to read from the foo;bar
file.
The closing }
can also not be followed by anything in the original implementation for no good reason.
Those are good reasons why you may want to use -e
(and for some reason you want your command to be on a single line; in the csh shell from the late '70s, it was a real pain entering a multi-line command).
But in your:
sed -e 's/-/_/g' -e ':a; N; $!ba; s/\n//g' input.txt
You're using -e
in places where it's not needed and not when they are needed (in non-GNU sed
implementations).
Besides the fact that you're running those commands in the wrong order, syntactically, that code is neither standard nor portable. You should not have other commands on the same line after the :
and b
commands.
It also ends up loading the whole input in the pattern space, the size of which is limited in most sed
implementations (and where it's not, it could end up using up all the system's memory for no good reason).
sed '
:a
$ ! {
N
b a
}
y/-/_/
s/\n//g' input.txt
Or if you need it on one line:
sed -e :a -e '$!{N;ba' -e '}' -e 'y/-/_/; s/\n//g' input
Would be syntactically and functionally correct with standard sed
but load the whole input in memory.
But here you only need:
<input tr - _ | paste -sd '\0' -
Where tr
tr
ansliterates -
s to _
s and paste
joins the lines together each not using up more than a small buffer of memory.
Beware though that except with the busybox implementation of paste
, that produces a non-empty output (one empty line) for an empty input.
If you're ready to swap those '70s tools with one from the (late) '80s (though it has kept evolving since), you could do:
perl -pe 'tr/-/_/; chomp unless eof' < input
Where we tr
ansliterate -
s to _
s (for sed
devotees, y
is provided as a synonym for tr
) and chomp
s (removes the line delimiter from) all lines except the last.
perl -p
is perl
's sed
mode, but contrary to sed
, perl
keeps the input line delimiter in the pattern space ($_
in perl
), which allows us to remove it conditionally.
:a; N; $!ba; s/\n//g'
is to define a label calleda; N; $!ba
.sed -z 's/-/_/g; s/\n//g'
should do what you want at least with GNU sed.\n
but I didn't work without the-z
option. For future reference: unix.stackexchange.com/q/26284/589116