Bash Script para repetir cada palavra em uma linha?

13

Eu tenho uma string como: dog cat bird whale

E eu quero pegar dog dog cat cat bird bird whale whale

Todas as palavras estão na mesma linha. Qualquer ideia?

Cristian
fonte

Respostas:

28

Adicionando à família de soluções :-).

duplicator.sh:

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

Torne executável e agora:

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

Alternativamente, como uma função shell, por exemplo, para ser reutilizável dentro de um script:

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

que pode ser executado diretamente onde definido como

duplicator dog cat bird whale
Daniel Andersson
fonte
4
Eu gosto da simplicidade da abordagem de shell.
Henk Langeveld
Bem, eu realmente amo a simplicidade desta solução
Cristian
Muito melhor do que as duas primeiras maneiras em que pensei.
Joe
21

Você poderia usar sed:

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

Se você deseja salvar as alterações no arquivo no local, diga:

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

Você também pode usar perl:

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

(Adicione a -iopção para salvar as alterações no arquivo no local.)

Ou, como sugerido por terdon :

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

Citação de perlvar:

@F

A matriz @Fcontém os campos de cada linha lida quando o modo de divisão automática está ativado. Veja perlrun para o -aswitch. Essa matriz é específica do pacote e deve ser declarada ou ter um nome completo do pacote, se não estiver no pacote principal quando estiver sendo executado strict 'vars'.

devnull
fonte
4
Shorter: sed -r 's/\S+/& &/g'.
Cyrus
2
Esse não é um script do Bash. A invocação de algum comando (mesmo de um shell Bash) não consiste em um script Bash.
Peter Mortensen
4
@PeterMortensen Você está tecnicamente correto (o melhor tipo de correção), mas praticamente todos os sistemas que possuem o bash instalado também terão as ferramentas padrão do unix, incluindo sed e awk. O ponto principal do script de shell (bem, grande parte disso) é apontar os comandos para o lugar certo.
precisa saber é
3
@PeterMortensen Isso está incorreto. Um script bash pode chamar comandos externos. Um script bash deve começar com uma linha shebang, mas isso não é estritamente necessário. A questão não especificou que o script bash não deveria chamar comandos externos (geralmente chamado de "script bash puro").
Gilles
2
Bom truque perl. Você pode torná-lo mais curto com -a:perl -M5.10.0 -ane 'say join " ", map{$_, $_} @F;'
terdon
4

O que seria isso sem uma awk/gawkresposta:

$ 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 

Se uma nova linha final for importante:

$ awk '{ for(i=1;i<=NF+1;i+=1/2) { printf("%s ",$i); }} END{print ""}' <<<"dog cat bird whale"
user19087
fonte
Meus primeiros votos negativos. Apenas curioso, por quê? Há algo de errado com o script? Ou uma versão mais abreviada?
user19087
Não é meu voto negativo, mas for(i=1;i<=NF;++i) printf "%s %s ",$i,$i;é mais curto e mais legível, IMHO.
Richard
Difícil argumentar com isso, por isso simplifiquei e abreviei minha resposta (agora aproveita os índices arredondados para ints) sem alterar a abordagem. Espero que agora seja mais claro.
precisa saber é o seguinte
1
Esse não é um script do Bash. A invocação de algum comando (mesmo de um shell Bash) não consiste em um script Bash.
22413 Peter
Colocar isso em um script é trivial, no entanto.
Kevin
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
fonte
1
Pensei em escrever sed -n 'p;p'- achei mais transparente o que estava fazendo.
glenn jackman
1
Você deve adicionar isso à resposta!
terdon
1

Se você tem sua string em uma variável, digamos foo="dog cat bird whale", você pode fazer:

  • Festa pura:

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

    Explicação: Os parênteses são necessários para que o reade echoaconteça no mesmo subshell e, portanto, possam compartilhar variáveis. Sem eles, echoapenas imprimiria uma linha em branco.

  • 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
    

    Explicação: O -osinalizador de joinpermite definir o formato de saída. Aqui, estou dizendo para imprimir o 1º campo do 1º arquivo ( 1.1), seguido pelo 2º campo do 1º arquivo ( 1.2) etc. Dessa forma, cada campo do 1º arquivo é impresso duas vezes. No entanto, joinfoi projetado para, bem, unir duas linhas de entrada em um campo comum. Então, eu também passo uma linha em branco ( <(echo)) e depois a ignoro. Os -jconjuntos do campo de associação, definindo-o como um que não existe (5º) faz com que joinpara imprimir toda a linha.

    Se você não se importa com espaço em branco ou a ordem de entrada, pode fazer

    $ 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
    

    Explicação:

    -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.
    

    O script acima salvará cada campo (de @F) duas vezes na matriz @ke depois será impresso @k. Se você não precisar da nova linha à direita, poderá simplificar para

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

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

    Explicação: A -0opção define o separador de registros de entrada (como um número hexadecimal ou octal, consulte aqui para conversões). Aqui, estou configurando para octal, 040que é um espaço. As -pmarcas perlimprimem cada "linha" de entrada e, como definimos o separador de registros como espaço, as linhas agora são definidas por espaços, para que cada campo seja impresso duas vezes.

  • awk:

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

    Explicação: NF é o número de campos; portanto, o script acima passa por cada campo e o anexa a si próprio. Feito isso, imprimimos a linha ( 1;é apenas uma abreviação para impressão).

Terdon
fonte
0

Agora, para uma pythonresposta:

Na linha de comando:

$ 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

Partida stdin:

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

O resultado nos dois casos:

dog dog cat cat bird bird whale whale
user19087
fonte
0

Um pouco exagerado, mas uma haskellresposta:

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

Outra abordagem, também usando apenas 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

Não vejo nenhum benefício comparado à resposta principal, apenas para mostrar uma maneira um pouco diferente, que pode ser mais adequada para alguns propósitos.

mpy
fonte
echotambém é um shell embutido no Bash (teste type echo).
Daniel Andersson
@DanielAndersson: Claro, foi por isso que escrevi " também usando apenas bash builtins", tendo em mente a sua boa resposta (já marcada com +1).
mpy
OK, você pode classificar meu comentário como confusão de idioma :-).
Daniel Andersson