11

Can sed make something like :

12345

become :

1&2&3&4&5

?

6 Answers 6

26

With GNU sed:

sed 's/./\&&/2g'

(substitute every (g) character (.) with the same (&) preceded with & (\&) but only starting from the second occurrence (2)).

Portably:

sed 's/./\&&/g;s/&//'

(replace every occurrence, but then remove the first & which we don't want).

With some awk implementations (not POSIX as the behaviour is unspecified for an empty FS):

awk -F '' -v OFS="&" '{$1=$1;print}'

(with gawk and a few other awk implementations, an empty field separator splits the records into its character constituents. The output field separator (OFS) is set to &. We assign a value to $1 (itself) to force the record to be regenerated with the new field separator before printing it, NF=NF also works and is slightly more efficient in many awk implementations but the behaviour when you do that is currently unspecified by POSIX).

perl:

perl -F -lape '$_=join"&",@F' 

(-pe runs the code for every line, and prints the result ($_); -l strips and re-adds line endings automatically; -a populates @F with input split on the delimiter set in -F, which here is an empty string. The result is to split every character into @F, then join them up with '&', and print the line.)

Alternatively:

perl -pe 's/(?<=.)./&$&/g' 

(replace every character provided it's preceded by another character (look-behind regexp operator (?<=...))

Using zsh shell operators:

in=12345
out=${(j:&:)${(s::)in}}

(again, split on an empty field separator using the s:: parameter expansion flag, and join with &)

Or:

out=${in///&} out=${out#?}

(replace every occurrence of nothing (so before every character) with & using the ${var//pattern/replacement} ksh operator (though in ksh an empty pattern means something else, and yet something else, I'm not sure what in bash), and remove the first one with the POSIX ${var#pattern} stripping operator).

Using ksh93 shell operators:

in=12345
out=${in//~(P:.(?=.))/\0&}

(~(P:perl-like-RE) being a ksh93 glob operator to use perl-like regular expressions (different from perl's or PCRE's though), (?=.) being the look-ahead operator: replace a character provided it's followed by another character with itself (\0) and &)

Or:

out=${in//?/&\0}; out=${out#?}

(replace every character (?) with & and itself (\0), and we remove the superflous one)

Using bash shell operators:

shopt -s extglob
in=12345
out=${in//@()/&}; out=${out#?}

(same as zsh's, except that you need @() there (a ksh glob operator for which you need extglob in bash)).

7
  • 2
    @AFSHIN, that wouldn't work on a 012345 input Aug 24, 2017 at 19:13
  • 1
    this should work awk -F '' -v OFS="&" 'NF=NF' Aug 25, 2017 at 1:28
  • 1
    @AFSHIN, but remove empty lines. More generally, when using an action as a condition and intend the result of the action to be printed, you need to make sure the value returned by the action isn't an empty string or a numerical string that resolves to 0. Aug 25, 2017 at 9:23
  • 1
    Could you add a quick explanation of how each of these work? It looks like there's some awesome things to learn here, but I don't even know where I'd begin researching most of them to see how to apply them outside the scope of this specific problem.
    – IMSoP
    Aug 25, 2017 at 12:51
  • 2
    @StéphaneChazelas Brilliant, thanks. Searching complex docs for things like sed is a bit of an art, so having some hands-on examples is a great way of learning new bits you hadn't seen before.
    – IMSoP
    Aug 25, 2017 at 14:09
15

Unix utilities:

fold -w1|paste -sd\& -

Explained:

"fold -w1" - will wrap an each input character to its own line

fold - wrap each input line to fit in specified width

-w, --width=WIDTH use WIDTH columns instead of 80

%echo 12345|fold -w1
1
2
3
4
5

"paste -sd\& -" - will merge the input lines together, using & as a separator

paste - merge lines of files

-s, --serial paste one file at a time instead of in parallel

-d, --delimiters=LIST reuse characters from LIST instead of TABs

%fold -w1|paste -sd\& -
1&2&3&4&5

(Note that if the input contains several lines, they'll be joined with &)

8
  • 2
    Fail on multibyte characters. Try echo "abcdeéèfg" | fold -1 | paste -sd\& -
    – user232326
    Aug 25, 2017 at 3:00
  • 3
    @Arrow Most probably you are just using a buggy coreutils version of fold, which does not have a full Unicode support. BSD fold, RedHat-patched versions of coreutils (i.e. Fedora or CentOS) as well as BusyBox implementation of it, can handle Unicode just nice.
    – zeppelin
    Aug 25, 2017 at 6:31
  • 5
    The question is specifically about sed.
    – Alexander
    Aug 25, 2017 at 8:11
  • 6
    @Alexander - that is true, and there is a number of good sed answers available below. And I don't see any harm in demonstrating how the task can be solved by the other means.
    – zeppelin
    Aug 25, 2017 at 8:39
  • @StéphaneChazelas >POSIXly, you'd need fold -w 1 True, I've added "-w", thx ! "-", in turn, is not required If no file operands are specified, the standard input shall be used
    – zeppelin
    Aug 25, 2017 at 8:55
11

Use sed

sed 's/./&\&/g;s/.$//'
0
9
sed 's/\B/\&/g'

\B - Matches everywhere but on a word boundary; that is it matches if the character to the left and the character to the right are either both “word” characters or both “non-word” characters.

Information: GNU sed manual, regular expression extensions.

Testing:

sed 's/\B/\&/g' <<< '12345'
1&2&3&4&5
1
  • 5
    Interesting idea but the question does not say that the string does not contain a space, a dot or anything that could constitute a word boundary. It just says "between characters" which should be interpreted as "any characters".
    – xhienne
    Aug 25, 2017 at 8:36
4

This will be a little slower than some of the other answers, but it's pretty clear:

echo 12345 | perl -lnE 'say join "&", split //'
4

Here's another way. The first part of the sed expression captures every character then replaces that with the character and an ampersand. The second part removes the ampersand from the end of the line.

echo 12345 | sed -r 's/(.)/\1\&/g;s/\&$//g'
1&2&3&4&5

Works on multibyte characters too.

2
  • 1
    No need to call sed twice, a sed script may have several commands: sed -r 's/(.)/\1\&/g; s/\&$//g'
    – xhienne
    Aug 25, 2017 at 8:09
  • xhienne, thanks, TIL! Updated the answer.
    – Alexander
    Aug 25, 2017 at 8:10

You must log in to answer this question.