Bash Script to repeat every word in a line?

13

1

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

Posted 2014-03-06T18:29:20.563

Reputation: 131

Answers

28

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

Daniel Andersson

Posted 2014-03-06T18:29:20.563

Reputation: 20 465

4I like the simplicity of the shell approach. – Henk Langeveld – 2014-03-07T17:27:17.990

Well I really love the simplicity of this solution – Cristian – 2014-03-10T16:41:02.927

Way better than the first couple of ways I thought of. – Joe – 2014-03-10T21:17:59.980

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

Posted 2014-03-06T18:29:20.563

Reputation: 2 987

4Shorter: sed -r 's/\S+/& &/g'. – cYrus – 2014-03-06T18:53:11.887

2That is not a Bash script. Invoking some command (even from a Bash shell) does not consistute a Bash script. – Peter Mortensen – 2014-03-07T10:48:57.193

4@PeterMortensen You are technically correct (the best kind of correct), but practically every system that has bash installed will also have the standard unix tools, including sed and awk. The whole point of shell scripting (well, a large part of the point of it) is to point commands at the right place. – evilsoup – 2014-03-07T11:43:42.877

3@PeterMortensen This is incorrect. A bash script can call external commands. A bash script should start with a shebang line, but this is not strictly necessary. The question did not specify that the bash script should not call external commands (often called a “pure bash script”). – Gilles 'SO- stop being evil' – 2014-03-07T12:20:40.357

2Nice perl trick. You can make it shorter with -a: perl -M5.10.0 -ane 'say join " ", map{$_, $_} @F;' – terdon – 2014-03-07T14:35:53.813

@terdon Added your suggestion. – devnull – 2014-03-07T14:42:35.913

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"

user19087

Posted 2014-03-06T18:29:20.563

Reputation: 513

My firsts downvote. Just curious, why? Is there something wrong with the script? Or a more abbreviated version? – user19087 – 2014-03-06T21:26:08.747

Not my downvote, but for(i=1;i<=NF;++i) printf "%s %s ",$i,$i; is both shorter and more readable, IMHO. – rici – 2014-03-06T22:12:46.997

Hard to argue with that, so I've simplified and shortened my answer (now takes advantage of indices being rounded down to ints) without changing the approach. Hopefully it is now clearer. – user19087 – 2014-03-07T00:01:11.327

1That is not a Bash script. Invoking some command (even from a Bash shell) does not consistute a Bash script. – Peter Mortensen – 2014-03-07T10:55:53.400

Putting this in a script is trivial, though. – Kevin – 2014-03-08T00:20:05.840

I'd be leery about using a float as the increment. At some point you'll get 0.99999999 or 1.000000001 and you get one more/less iteration. – glenn jackman – 2014-03-30T14:31:23.403

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

Posted 2014-03-06T18:29:20.563

Reputation: 18 546

1I thought about writing sed -n 'p;p' -- I thought that was more transparent about what it's doing. – glenn jackman – 2014-03-06T19:22:31.170

1You should add that to the answer! – terdon – 2014-03-07T01:47:25.273

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

Posted 2014-03-06T18:29:20.563

Reputation: 45 216

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

Posted 2014-03-06T18:29:20.563

Reputation: 513

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

Posted 2014-03-06T18:29:20.563

Reputation: 513

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

Posted 2014-03-06T18:29:20.563

Reputation: 20 866

echo is also a shell builtin in Bash (test type echo). – Daniel Andersson – 2014-03-12T09:25:05.237

@DanielAndersson : Sure, that's why I wrote "also using only bash builtins", having your nice (already +1'd) answer in mind. – mpy – 2014-03-12T18:01:47.057

OK, you can classify my comment as language confusion :-) . – Daniel Andersson – 2014-03-12T18:26:14.490