13

I have a string like: dog cat bird whale

And I want to get dog dog cat cat bird bird whale whale

All the words are in the same line. Any idea?

Cristian
  • 131

8 Answers8

27

Adding to the family of solutions :-) .

duplicator.sh:

for i; do echo -n "$i $i "; done; echo

Make executable, and now:

$ ./duplicator.sh dog cat bird whale
dog dog cat cat bird bird whale whale

Alternatively as a shell function, e.g. to be reusable within a script:

duplicator() {
    for i; do echo -n "$i $i "; done; echo
}

which can then be run directly where defined as

duplicator dog cat bird whale
21

You could use sed:

sed -r 's/(\S+)/\1 \1/g' filename

If you want to save the changes to the file in-place, say:

sed -i -r 's/(\S+)/\1 \1/g' filename

You could also use perl:

perl -M5.10.0 -ne 'say join " ", map{$_, $_} split " ";' filename

(Add the -i option to save the changes to the file in-place.)

Or, as suggested by terdon:

perl -M5.10.0 -ane 'say join " ", map{$_, $_} @F;' filename

Quoting from perlvar:

@F

The array @F contains the fields of each line read in when autosplit mode is turned on. See perlrun for the -a switch. This array is package-specific, and must be declared or given a full package name if not in package main when running under strict 'vars'.

devnull
  • 3,405
4

What would this be without an awk/gawk answer:

$ awk '{ for(i=1;i<=NF+1;i+=1/2) { printf("%s ",$i); }}' <<<"dog cat bird whale"
dog dog cat cat bird bird whale whale 

If a terminating newline is important:

$ awk '{ for(i=1;i<=NF+1;i+=1/2) { printf("%s ",$i); }} END{print ""}' <<<"dog cat bird whale"
terdon
  • 54,564
user19087
  • 541
3
s="dog cat bird wale"
ss=$( tr ' ' '\n' <<< "$s" | sed p | tr '\n' ' ' )
echo "$ss"
dog dog cat cat bird bird wale wale 
glenn jackman
  • 27,524
1

If you have your string in an a variable, say foo="dog cat bird whale", you could do:

  • Pure bash:

    $ echo "$foo" | (read a b c d && echo "$a $a $b $b $c $c $d $d")
    dog dog cat cat bird bird whale whale
    

    Explanation: The parentheses are needed so that the read and echo happen in the same subshell and can therefore share variables. Without them, the echo would just print a blank line.

  • coreutils:

    $ join -j 5 -o 1.1,1.1,1.2,1.2,1.3,1.3,1.4,1.4 <(echo $foo) <(echo)
    dog dog cat cat bird bird whale whale
    

    Explanation: The -o flag of join allows you to set the output format. Here, I'm telling it to print the 1st field of the 1st file (1.1), followed by the 2nd field of the 1st file (1.2) etc. That way each field of the 1st file is printed twice. However, join is designed to, well, join two input lines on a common field. So, I also pass it a blank line (<(echo)) and then ignore it. The -j sets the join field, setting it to one that does not exist (the 5th) causes join to print the entire line.

    If you don't care about whitespace or the input order, you could do

    $ paste <(echo $foo) <(echo $foo)
    dog cat bird wale   dog cat bird wale
    
  • Perl 1:

    $ echo $foo | perl -lane 'push @k, $_,$_ for @F; print "@k"'
    dog dog cat cat bird bird whale whale
    

    Explanation:

    -l: adds a newline to each print call (among other things)
    -a: turns on field splitting, fields are saved as @F
    -n: process input line by line
    -e: give a script as a command line parameter.
    

    The script above will save each field (from @F) twice in the array @k and then print @k. If you don't need the trailing newline, you could simplify to

    $ echo $foo | perl -ane 'print " $_ $_" for @F'
    
  • Perl 2:

    $ echo $foo | perl -0040 -pne 'print "$_"' | paste - - 
    dog dog cat cat bird bird whale whale
    

    Explanation: The -0 option sets the input record separator (as a hexadecimal or octal number, see here for conversions). Here, I am setting it to octal 040 which is a space. The -p makes perl print each input "line" and since we have set the record separator to space, lines are now defined by spaces, so every field is printed twice.

  • awk:

    $ echo $foo | awk '{for(i=1;i<=NF;i++){$i=$i" "$i;} 1;}'
    dog dog cat cat bird bird whale whale
    

    Explanation: NF is the number of fields, so the script above goes through each field and appends it to itself. Once that's done, we print the line (1; is just shorthand for print).

terdon
  • 54,564
0

Now for a python answer:

From the command line:

$ python -c "import sys; s=sys.argv[1:]; print(' '.join(j for i in zip(s,s)for j in i));" dog cat bird whale

From stdin:

$ python -c "s=input().split(); print(' '.join(j for i in zip(s,s)for j in i));" <<<"dog cat bird whale"

The result in both cases:

dog dog cat cat bird bird whale whale
user19087
  • 541
0

Slightly over-the-top, but a haskell answer:

$ ghc -e "getLine >>= putStrLn . unwords . (concatMap $ replicate 2) . words" <<<"dog cat bird whale"
dog dog cat cat bird bird whale whale
user19087
  • 541
0

Another approach, also using only bash builtins

$ string="dog cat bird whale"
$ twix() { while [[ ! -z $1 ]]; do printf "%s %s " $1 $1; shift; done; }
$ twix $string
dog dog cat cat bird bird whale whale

I don't see any benefits compared to the top answer, just to show a slightly different way, which might be better suitable for some purposes.

mpy
  • 28,816