Como posso repetir um caractere no Bash?


240

Como eu poderia fazer isso echo?

perl -E 'say "=" x 100'

Infelizmente isso não é Bash.
solidsnack

1
não com eco, mas sobre o mesmo tema ruby -e 'puts "=" * 100'oupython -c 'print "=" * 100'
Evgeny

1
Ótima pergunta. Respostas muito boas. Eu usei uma das respostas em um trabalho real aqui, que vou postar como exemplo: github.com/drbeco/oldfiles/blob/master/oldfiles (usado printfcom seq)svrb=`printf '%.sv' $(seq $vrb)`
Dr. Beco

Uma solução genérica para imprimir qualquer que seja (1 ou mais caracteres, inclusive novas linhas): Repeat_this () {i = 1; enquanto ["$ i" -le "$ 2"]; imprimaf "% s" "$ 1"; i = $ (($ i + 1)); feito ; printf '\ n';}. Use desta forma: Repita_este "algo" Número_de_repetições. Por exemplo, para mostrar 5 vezes a repetição, incluindo 3 novas linhas: Repeat_this "$ (printf '\ n \ n \ n \ nthis')" 5. O printf final \ \ n 'pode ser retirado (mas eu o coloquei para criar arquivos de texto, e eles precisam de uma nova linha como último caractere!)
Olivier Dulac

Respostas:


396

Você pode usar:

printf '=%.0s' {1..100}

Como isso funciona:

O Bash se expande {1..100} para que o comando se torne:

printf '=%.0s' 1 2 3 4 ... 100

Eu configurei o formato printf para o =%.0sque significa que ele sempre imprimirá um único, =independentemente do argumento que for fornecido. Portanto, imprime 100 =s.


14
Ótima solução que executa razoavelmente bem, mesmo com grandes contagens de repetição. Aqui está uma função wrapper você pode invocar com repl = 100, por exemplo ( evalé exigido truques, infelizmente, para basear a expansão cinta em uma variável):repl() { printf "$1"'%.s' $(eval "echo {1.."$(($2))"}"); }
mklement0

7
É possível definir o limite superior usando um var? Eu tentei e não consigo fazê-lo funcionar.
Mike Purcell

70
Você não pode usar variáveis ​​na expansão de chaves. Use, seqpor exemplo, por exemplo $(seq 1 $limit).
dogbane

11
Se você funcionalizar isso, é melhor reorganizá-lo $s%.0spara %.0s$shífen, causando um printferro.
KomodoDave 30/07/2014

5
Isso me fez notar um comportamento do Bash printf: ele continua a aplicar a string de formato até que não haja mais argumentos. Eu tinha assumido que processou a string de formato apenas uma vez!
Jeenu

89

Não é fácil. Mas por exemplo:

seq -s= 100|tr -d '[:digit:]'

Ou talvez uma maneira de conformidade padrão:

printf %100s |tr " " "="

Há também um tput rep, mas quanto aos meus terminais em mãos (xterm e linux), eles não parecem suportá-lo :)


3
Observe que a primeira opção com seq imprime um a menos que o número fornecido, portanto esse exemplo imprime 99 =caracteres.
Camilo Martin

13
printf tré a única solução POSIX porque seq, yese {1..3}não é POSIX.
Ciro Santilli escreveu:

2
Para repetir uma seqüência de caracteres em vez de apenas um caractere: printf %100s | sed 's/ /abc/g'- gera 'abcabcabc ...'
John Rix

3
+1 por não usar loops e apenas um comando externo ( tr). Você também pode estendê-lo a algo como printf "%${COLUMNS}s\n" | tr " " "=".
musiphil

2
@ mklement0 Bem, eu esperava que você estivesse contando a última nova linha por engano wc. A única conclusão que posso tirar disso é " seqnão deve ser usada".
Camilo Martin

51

Ponta do chapéu para @ gniourf_gniourf por sua contribuição.

Nota: Esta resposta não responde à pergunta original, mas complementa as respostas úteis existentes comparando o desempenho .

As soluções são comparadas apenas em termos de velocidade de execução - os requisitos de memória não são levados em consideração (eles variam entre as soluções e podem importar com grandes contagens de repetição).

Resumo:

  • Se sua contagem de repetições for pequena , digamos até cerca de 100, vale a pena usar as soluções Bash-only , já que o custo de inicialização de utilitários externos é importante, principalmente do Perl.
    • Pragmaticamente falando, no entanto, se você precisar apenas de uma instância de caracteres repetidos, todas as soluções existentes podem ser boas.
  • Com grandes contagens de repetição , use utilitários externos , pois eles serão muito mais rápidos.
    • Em particular, evite a substituição global de substring do Bash por grandes cadeias
      (por exemplo, ${var// /=}), pois é proibitivamente lento.

A seguir, são mostrados os tempos do iMac de final de 2012 com uma CPU Intel Core i5 de 3,2 GHz e uma unidade Fusion, executando o OSX 10.10.4 e o bash 3.2.57, e são a média de 1000 execuções.

As entradas são:

  • listado em ordem crescente de duração da execução (mais rápido primeiro)
  • prefixado com:
    • M... uma solução potencialmente com vários caracteres
    • S... uma solução única para caracteres
    • P ... uma solução compatível com POSIX
  • seguido de uma breve descrição da solução
  • com o nome do autor da resposta de origem

  • Contagem pequena de repetições: 100
[M, P] printf %.s= [dogbane]:                           0.0002
[M   ] printf + bash global substr. replacement [Tim]:  0.0005
[M   ] echo -n - brace expansion loop [eugene y]:       0.0007
[M   ] echo -n - arithmetic loop [Eliah Kagan]:         0.0013
[M   ] seq -f [Sam Salisbury]:                          0.0016
[M   ] jot -b [Stefan Ludwig]:                          0.0016
[M   ] awk - $(count+1)="=" [Steven Penny (variant)]:   0.0019
[M, P] awk - while loop [Steven Penny]:                 0.0019
[S   ] printf + tr [user332325]:                        0.0021
[S   ] head + tr [eugene y]:                            0.0021
[S, P] dd + tr [mklement0]:                             0.0021
[M   ] printf + sed [user332325 (comment)]:             0.0021
[M   ] mawk - $(count+1)="=" [Steven Penny (variant)]:  0.0025
[M, P] mawk - while loop [Steven Penny]:                0.0026
[M   ] gawk - $(count+1)="=" [Steven Penny (variant)]:  0.0028
[M, P] gawk - while loop [Steven Penny]:                0.0028
[M   ] yes + head + tr [Digital Trauma]:                0.0029
[M   ] Perl [sid_com]:                                  0.0059
  • As soluções exclusivas do Bash lideram o grupo - mas apenas com uma contagem repetida tão pequena! (ver abaixo).
  • O custo de inicialização de utilitários externos é importante aqui, especialmente o Perl. Se você precisar chamar isso em um loop - com pequenas contagens de repetição em cada iteração - evite as multi-utilidades awke perlsoluções.

  • Grande número de repetições: 1000000 (1 milhão)
[M   ] Perl [sid_com]:                                  0.0067
[M   ] mawk - $(count+1)="=" [Steven Penny (variant)]:  0.0254
[M   ] gawk - $(count+1)="=" [Steven Penny (variant)]:  0.0599
[S   ] head + tr [eugene y]:                            0.1143
[S, P] dd + tr [mklement0]:                             0.1144
[S   ] printf + tr [user332325]:                        0.1164
[M, P] mawk - while loop [Steven Penny]:                0.1434
[M   ] seq -f [Sam Salisbury]:                          0.1452
[M   ] jot -b [Stefan Ludwig]:                          0.1690
[M   ] printf + sed [user332325 (comment)]:             0.1735
[M   ] yes + head + tr [Digital Trauma]:                0.1883
[M, P] gawk - while loop [Steven Penny]:                0.2493
[M   ] awk - $(count+1)="=" [Steven Penny (variant)]:   0.2614
[M, P] awk - while loop [Steven Penny]:                 0.3211
[M, P] printf %.s= [dogbane]:                           2.4565
[M   ] echo -n - brace expansion loop [eugene y]:       7.5877
[M   ] echo -n - arithmetic loop [Eliah Kagan]:         13.5426
[M   ] printf + bash global substr. replacement [Tim]:  n/a
  • A solução Perl da pergunta é de longe a mais rápida.
  • A substituição global de strings ( ${foo// /=}) do Bash é inexplicavelmente dolorosamente lenta com strings grandes e foi tirada da corrida (demorou cerca de 50 minutos (!) No Bash 4.3.30 e ainda mais no Bash 3.2.57 - nunca esperei para terminar).
  • Os loops de bash são lentos e os loops aritméticos ( (( i= 0; ... ))) são mais lentos que os com extensão de chaves ( {1..n}) - embora os loops aritméticos sejam mais eficientes em termos de memória.
  • awkrefere-se ao BSD awk (como também encontrado no OSX) - é visivelmente mais lento que o gawk(GNU Awk) e especialmente mawk.
  • Observe que com contagens grandes e vários caracteres. seqüências de caracteres, o consumo de memória pode se tornar uma consideração - as abordagens diferem nesse sentido.

Aqui está o script Bash ( testrepeat) que produziu o acima. São necessários 2 argumentos:

  • a contagem de repetição de caracteres
  • opcionalmente, o número de execuções de teste para executar e calcular o tempo médio de

Em outras palavras: os tempos acima foram obtidos com testrepeat 100 1000etestrepeat 1000000 1000

#!/usr/bin/env bash

title() { printf '%s:\t' "$1"; }

TIMEFORMAT=$'%6Rs'

# The number of repetitions of the input chars. to produce
COUNT_REPETITIONS=${1?Arguments: <charRepeatCount> [<testRunCount>]}

# The number of test runs to perform to derive the average timing from.
COUNT_RUNS=${2:-1}

# Discard the (stdout) output generated by default.
# If you want to check the results, replace '/dev/null' on the following
# line with a prefix path to which a running index starting with 1 will
# be appended for each test run; e.g., outFilePrefix='outfile', which
# will produce outfile1, outfile2, ...
outFilePrefix=/dev/null

{

  outFile=$outFilePrefix
  ndx=0

  title '[M, P] printf %.s= [dogbane]'
  [[ $outFile != '/dev/null' ]] && outFile="$outFilePrefix$((++ndx))"
  # !! In order to use brace expansion with a variable, we must use `eval`.
  eval "
  time for (( n = 0; n < COUNT_RUNS; n++ )); do 
    printf '%.s=' {1..$COUNT_REPETITIONS} >"$outFile"
  done"

  title '[M   ] echo -n - arithmetic loop [Eliah Kagan]'
  [[ $outFile != '/dev/null' ]] && outFile="$outFilePrefix$((++ndx))"
  time for (( n = 0; n < COUNT_RUNS; n++ )); do 
    for ((i=0; i<COUNT_REPETITIONS; ++i)); do echo -n =; done >"$outFile"
  done


  title '[M   ] echo -n - brace expansion loop [eugene y]'
  [[ $outFile != '/dev/null' ]] && outFile="$outFilePrefix$((++ndx))"
  # !! In order to use brace expansion with a variable, we must use `eval`.
  eval "
  time for (( n = 0; n < COUNT_RUNS; n++ )); do 
    for i in {1..$COUNT_REPETITIONS}; do echo -n =; done >"$outFile"
  done
  "

  title '[M   ] printf + sed [user332325 (comment)]'
  [[ $outFile != '/dev/null' ]] && outFile="$outFilePrefix$((++ndx))"
  time for (( n = 0; n < COUNT_RUNS; n++ )); do 
    printf "%${COUNT_REPETITIONS}s" | sed 's/ /=/g' >"$outFile"
  done


  title '[S   ] printf + tr [user332325]'
  [[ $outFile != '/dev/null' ]] && outFile="$outFilePrefix$((++ndx))"
  time for (( n = 0; n < COUNT_RUNS; n++ )); do 
    printf "%${COUNT_REPETITIONS}s" | tr ' ' '='  >"$outFile"
  done


  title '[S   ] head + tr [eugene y]'
  [[ $outFile != '/dev/null' ]] && outFile="$outFilePrefix$((++ndx))"
  time for (( n = 0; n < COUNT_RUNS; n++ )); do 
    head -c $COUNT_REPETITIONS < /dev/zero | tr '\0' '=' >"$outFile"
  done


  title '[M   ] seq -f [Sam Salisbury]'
  [[ $outFile != '/dev/null' ]] && outFile="$outFilePrefix$((++ndx))"
  time for (( n = 0; n < COUNT_RUNS; n++ )); do 
    seq -f '=' -s '' $COUNT_REPETITIONS >"$outFile"
  done


  title '[M   ] jot -b [Stefan Ludwig]'
  [[ $outFile != '/dev/null' ]] && outFile="$outFilePrefix$((++ndx))"
  time for (( n = 0; n < COUNT_RUNS; n++ )); do 
    jot -s '' -b '=' $COUNT_REPETITIONS >"$outFile"
  done


  title '[M   ] yes + head + tr [Digital Trauma]'
  [[ $outFile != '/dev/null' ]] && outFile="$outFilePrefix$((++ndx))"
  time for (( n = 0; n < COUNT_RUNS; n++ )); do 
    yes = | head -$COUNT_REPETITIONS | tr -d '\n'  >"$outFile"
  done

  title '[M   ] Perl [sid_com]'
  [[ $outFile != '/dev/null' ]] && outFile="$outFilePrefix$((++ndx))"
  time for (( n = 0; n < COUNT_RUNS; n++ )); do 
    perl -e "print \"=\" x $COUNT_REPETITIONS" >"$outFile"
  done

  title '[S, P] dd + tr [mklement0]'
  [[ $outFile != '/dev/null' ]] && outFile="$outFilePrefix$((++ndx))"
  time for (( n = 0; n < COUNT_RUNS; n++ )); do 
    dd if=/dev/zero bs=$COUNT_REPETITIONS count=1 2>/dev/null | tr '\0' "=" >"$outFile"
  done

  # !! On OSX, awk is BSD awk, and mawk and gawk were installed later.
  # !! On Linux systems, awk may refer to either mawk or gawk.
  for awkBin in awk mawk gawk; do
    if [[ -x $(command -v $awkBin) ]]; then

      title "[M   ] $awkBin"' - $(count+1)="=" [Steven Penny (variant)]'
      [[ $outFile != '/dev/null' ]] && outFile="$outFilePrefix$((++ndx))"
      time for (( n = 0; n < COUNT_RUNS; n++ )); do 
        $awkBin -v count=$COUNT_REPETITIONS 'BEGIN { OFS="="; $(count+1)=""; print }' >"$outFile"
      done

      title "[M, P] $awkBin"' - while loop [Steven Penny]'
      [[ $outFile != '/dev/null' ]] && outFile="$outFilePrefix$((++ndx))"
      time for (( n = 0; n < COUNT_RUNS; n++ )); do 
        $awkBin -v count=$COUNT_REPETITIONS 'BEGIN { while (i++ < count) printf "=" }' >"$outFile"
      done

    fi
  done

  title '[M   ] printf + bash global substr. replacement [Tim]'
  [[ $outFile != '/dev/null' ]] && outFile="$outFilePrefix$((++ndx))"
  # !! In Bash 4.3.30 a single run with repeat count of 1 million took almost
  # !! 50 *minutes*(!) to complete; n Bash 3.2.57 it's seemingly even slower -
  # !! didn't wait for it to finish.
  # !! Thus, this test is skipped for counts that are likely to be much slower
  # !! than the other tests.
  skip=0
  [[ $BASH_VERSINFO -le 3 && COUNT_REPETITIONS -gt 1000 ]] && skip=1
  [[ $BASH_VERSINFO -eq 4 && COUNT_REPETITIONS -gt 10000 ]] && skip=1
  if (( skip )); then
    echo 'n/a' >&2
  else
    time for (( n = 0; n < COUNT_RUNS; n++ )); do 
      { printf -v t "%${COUNT_REPETITIONS}s" '='; printf %s "${t// /=}"; } >"$outFile"
    done
  fi
} 2>&1 | 
 sort -t$'\t' -k2,2n | 
   awk -F $'\t' -v count=$COUNT_RUNS '{ 
    printf "%s\t", $1; 
    if ($2 ~ "^n/a") { print $2 } else { printf "%.4f\n", $2 / count }}' |
     column -s$'\t' -t

É interessante ver a comparação do tempo, mas acho que em muitos programas a saída é armazenada em buffer, portanto, o tempo pode ser alterado se o buffer estiver desativado.
precisa saber é o seguinte

In order to use brace expansion with a variable, we must use `eval`Py
pyb 15/05/19

2
Portanto, a solução perl (sid_com) é basicamente a mais rápida ... quando a sobrecarga inicial do lançamento do perl for atingida. (passa de 59ms para uma pequena repetição a 67ms para um milhão de repetições ... então o forl forl levou aproximadamente 59ms no seu sistema)
Olivier Dulac

46

Há mais de uma maneira de fazer isso.

Usando um loop:

  • A expansão entre chaves pode ser usada com literais inteiros:

    for i in {1..100}; do echo -n =; done    
  • Um loop tipo C permite o uso de variáveis:

    start=1
    end=100
    for ((i=$start; i<=$end; i++)); do echo -n =; done
    

Usando o printfbuiltin:

printf '=%.0s' {1..100}

A especificação de uma precisão aqui trunca a string para caber na largura especificada ( 0). Como printfreutiliza a string de formato para consumir todos os argumentos, isso simplesmente imprime "="100 vezes.

Usando head( printf, etc) e tr:

head -c 100 < /dev/zero | tr '\0' '='
printf %100s | tr " " "="

2
++ para a solução head/ tr, que funciona bem mesmo com altas contagens de repetição (ressalva pequena: head -cnão é compatível com POSIX, mas o BSD e o GNU o headimplementam); embora as outras duas soluções sejam lentas nesse caso, elas também têm a vantagem de trabalhar com seqüências de vários caracteres.
usar o seguinte comando

1
Usando yese head- útil se você quiser um certo número de novas linhas: yes "" | head -n 100. trpode fazê-lo imprimir qualquer caractere:yes "" | head -n 100 | tr "\n" "="; echo
loxaxs 27/05

Surpreendentemente: dd if=/dev/zero count=1 bs=100000000 | tr '\0' '=' >/dev/nullé significativamente mais lento que a head -c100000000 < /dev/zero | tr '\0' '=' >/dev/nullversão. Claro que você precisa usar um tamanho de bloco de 100M + para medir a diferença de tempo razoavelmente. 100M bytes levam 1,7 se 1 s com as duas versões respectivas mostradas. Tirei o tr e apenas o joguei /dev/nulle obtive 0,287 s para a headversão e 0,675 s para a ddversão por um bilhão de bytes.
Michael Goldshteyn

Para: dd if=/dev/zero count=1 bs=100000000 | tr '\0' '=' >/dev/null=> 0,21332 s, 469 MB/s; Para: dd if=/dev/zero count=100 bs=1000000| tr '\0' '=' >/dev/null=> 0,161579 s, 619 MB/s;
3ED 18/08/18

31

Acabei de encontrar uma maneira muito fácil de fazer isso usando seq:

UPDATE: Funciona no BSD seqque acompanha o OS X. YMMV com outras versões

seq  -f "#" -s '' 10

Imprimirá '#' 10 vezes, assim:

##########
  • -f "#"define a string de formato para ignorar os números e apenas imprimir #para cada um.
  • -s '' define o separador como uma sequência vazia para remover as novas linhas que seq insere entre cada número
  • Os espaços depois -fe -sparecem ser importantes.

EDIT: Aqui está em uma função útil ...

repeat () {
    seq  -f $1 -s '' $2; echo
}

Que você pode chamar assim ...

repeat "#" 10

NOTA: Se você estiver repetindo #, as aspas são importantes!


7
Isso me dá seq: format ‘#’ has no % directive. seqé para números, não para strings. Veja gnu.org/software/coreutils/manual/html_node/seq-invocation.html
John B

Ah, então eu estava usando a versão BSD do seq encontrada no OS X. Vou atualizar a resposta. Qual versão você está usando?
Sam Salisbury

Estou usando o seq do GNU coreutils.
John B

1
@ JohnB: O BSD seqestá sendo reutilizado de maneira inteligente aqui para replicar as strings : a string de formato passada para -f- normalmente usada para formatar os números que estão sendo gerados - contém apenas a string a ser replicada aqui, para que a saída contenha apenas cópias dessa string. Infelizmente, o GNU seqinsiste na presença de um formato numérico na string de formato, que é o erro que você está vendo.
usar o seguinte comando

1
Bem feito; também funciona com cadeias de caracteres múltiplos . Use "$1"(aspas duplas), para que você também possa passar caracteres como '*'e strings com espaço em branco incorporado. Finalmente, se você quiser poder usá- %lo, precisará dobrá- lo (caso contrário, seqvocê achará que faz parte de uma especificação de formato como %f); usando "${1//%/%%}"iria cuidar disso. Como (como você mencionou) você está usando o BSD seq , isso funcionará em sistemas operacionais semelhantes ao BSD em geral (por exemplo, FreeBSD) - por outro lado, não funcionará no Linux , onde o GNU seq é usado.
usar o seguinte comando

18

Aqui estão duas maneiras interessantes:

ubuntu @ ubuntu: ~ $ yes = | cabeça -10 | colar -s -d '' -
==========
ubuntu @ ubuntu: ~ $ yes = | cabeça -10 | tr-d "\ n"
========== ubuntu @ ubuntu: ~ $ 

Observe que esses dois são sutilmente diferentes - O pastemétodo termina em uma nova linha. O trmétodo não.


1
Bem feito; observe que o BSD paste inexplicavelmente requer -d '\0'a especificação de um delimitador vazio e falha com -d ''- -d '\0'deve funcionar com todas as pasteimplementações compatíveis com POSIX e de fato também funciona com o GNU paste .
usar o seguinte comando

De espírito semelhante, com menos ferramentas externas:yes | mapfile -n 100 -C 'printf = \#' -c 1
bispo

@ bispo: Embora seu comando realmente crie menos um subshell, ainda é mais lento para contagens mais altas de repetições e, para contagens mais baixas, a diferença provavelmente não importa; o limite exato provavelmente depende do hardware e do sistema operacional, por exemplo, na minha máquina OSX 10.11.5, essa resposta já é mais rápida em 500; tente time yes = | head -500 | paste -s -d '\0' -; time yes | mapfile -n 500 -C 'printf = \#' -c 1. Mais importante, porém: se você estiver usando printfqualquer maneira, assim como você pode ir com a abordagem mais simples e mais eficiente da resposta aceita:printf '%.s=' $(seq 500)
mklement0

13

Não existe uma maneira simples. Evite loops usando printfe substituindo.

str=$(printf "%40s")
echo ${str// /rep}
# echoes "rep" 40 times.

2
Bom, mas só funciona razoavelmente com pequenas contagens de repetição. Aqui está uma função de invólucro que pode ser invocado como repl = 100, por exemplo (não emite um à direita \n):repl() { local ts=$(printf "%${2}s"); printf %s "${ts// /$1}"; }
mklement0

1
@ mklement0 Que bom que você forneceu versões funcionais das duas soluções, +1 nas duas!
Camilo Martin

8

Se você deseja conformidade e consistência POSIX em diferentes implementações de echoe printf, e / ou shells, além de bash:

seq(){ n=$1; while [ $n -le $2 ]; do echo $n; n=$((n+1)); done ;} # If you don't have it.

echo $(for each in $(seq 1 100); do printf "="; done)

... produzirá a mesma saída que perl -E 'say "=" x 100'praticamente em qualquer lugar.


1
O problema é que seqnão é um utilitário POSIX (embora os sistemas BSD e Linux tenham implementações dele) - você pode fazer a aritmética do shell POSIX com um whileloop, como na resposta do @ Xennex81 (com printf "=", como você sugere corretamente, em vez de echo -n).
usar o seguinte comando

1
Opa, você está certo. Às vezes, coisas assim passam despercebidas, pois esse padrão não faz nenhum sentido. calé POSIX. seqnão é. De qualquer forma, em vez de reescrever a resposta com um loop while (como você diz, isso já está em outras respostas), adicionarei uma função RYO. Mais educativo assim ;-).
Geoff Nixon

8

A pergunta era sobre como fazer isso com echo:

echo -e ''$_{1..100}'\b='

Isso fará exatamente o mesmo que perl -E 'say "=" x 100'com echoapenas.


Agora isso é incomum, se você não colocar espaços extras no espaço .. ou limpe usando: echo -e $ _ {1..100} '\ b =' | col
anthony

1
Péssima ideia. Isso falhará se $_1, $_2ou qualquer outra das centenas de variáveis ​​tiver valores.
John Kugelman

@JohnKugelman echo $ (set -; eval echo -e \ $ {{1..100}} '\\ b =')
mug896

Isso é nojento . Eu adoro: D
dimo414

6

Uma maneira pura do Bash sem eval, sem subcascas, sem ferramentas externas, sem expansões de chaves (ou seja, você pode ter o número para repetir em uma variável):

Se você receber uma variável nque se expande para um número (não negativo) e uma variável pattern, por exemplo,

$ n=5
$ pattern=hello
$ printf -v output '%*s' "$n"
$ output=${output// /$pattern}
$ echo "$output"
hellohellohellohellohello

Você pode fazer uma função com isso:

repeat() {
    # $1=number of patterns to repeat
    # $2=pattern
    # $3=output variable name
    local tmp
    printf -v tmp '%*s' "$1"
    printf -v "$3" '%s' "${tmp// /$2}"
}

Com este conjunto:

$ repeat 5 hello output
$ echo "$output"
hellohellohellohellohello

Para este pequeno truque, estamos usando printfbastante:

  • -v varname: em vez de imprimir na saída padrão, printfcolocará o conteúdo da sequência formatada em variável varname.
  • '% * s': printfusará o argumento para imprimir o número correspondente de espaços. Por exemplo, printf '%*s' 42imprimirá 42 espaços.
  • Finalmente, quando tivermos o número desejado de espaços em nossa variável, usamos um parâmetro de expansão para substituir todos os espaços pelo nosso padrão: ${var// /$pattern}expandiremos para a expansão de varcom todos os espaços substituídos pela expansão de $pattern.

Você também pode se livrar da tmpvariável na repeatfunção usando expansão indireta:

repeat() {
    # $1=number of patterns to repeat
    # $2=pattern
    # $3=output variable name
    printf -v "$3" '%*s' "$1"
    printf -v "$3" '%s' "${!3// /$2}"
}

Variação interessante para passar o nome da variável. Embora esta solução seja adequada para contagens repetidas de até 1.000 (e, portanto, provavelmente adequada para a maioria das aplicações da vida real, se eu adivinhar), ela fica muito lenta para contagens mais altas (veja a próxima Comente).
usar o seguinte comando

Parece que bashas operações globais de substituição de cadeia de caracteres no contexto da expansão de parâmetros ( ${var//old/new}) são particularmente lentas: extremamente lenta no bash 3.2.57e lenta no bash 4.3.30, pelo menos no meu sistema OSX 10.10.3 em uma máquina Intel Core i5 de 3,2 Ghz: com uma contagem de 1.000, as coisas são lentas ( 3.2.57) / rápidas ( 4.3.30): 0.1 / 0.004 segundos. Aumentar a contagem para 10.000 gera números surpreendentemente diferentes: repeat 10000 = varleva cerca de 80 segundos (!) No bash 3.2.57e cerca de 0,3 segundos no bash 4.3.30(muito mais rápido que no 3.2.57, mas ainda lento).
precisa saber é o seguinte

6
#!/usr/bin/awk -f
BEGIN {
  OFS = "="
  NF = 100
  print
}

Ou

#!/usr/bin/awk -f
BEGIN {
  while (z++ < 100) printf "="
}

Exemplo


3
Bem feito; isso é compatível com POSIX e razoavelmente rápido, mesmo com altas contagens de repetição, além de suportar seqüências de vários caracteres. Aqui está a versão shell: awk 'BEGIN { while (c++ < 100) printf "=" }'. Envolto em uma função parametrizada concha (invocação como repeat 100 =, por exemplo): repeat() { awk -v count="$1" -v txt=".$2" 'BEGIN { txt=substr(txt, 2); while (i++ < count) printf txt }'; }. (O manequim .prefixo caractere e complementar substrchamada são necessários para o trabalho em torno de um bug no BSD awk, onde passando um valor variável que começa com =quebras de comando.)
mklement0

1
A NF = 100solução é muito inteligente (apesar de obter 100 =, você deve usar NF = 101). As advertências são de que ele trava BSD awk(mas é muito rápido com gawke até mesmo mais rápido com mawk), e que discute POSIX nem atribuir a NF, nem o uso de campos em BEGINblocos. Você pode fazê-lo funcionar no BSD awktambém com um pequeno ajuste: awk 'BEGIN { OFS = "="; $101=""; print }'(mas curiosamente, no BSD awkque não é mais rápido que a solução de loop). Como uma solução de shell parametrizado: repeat() { awk -v count="$1" -v txt=".$2" 'BEGIN { OFS=substr(txt, 2); $(count+1)=""; print }'; }.
mklement0

Nota para os usuários - O truque NF = 100 causa uma falha de segmento no awk mais antigo. O original-awké o nome no Linux do awk mais antigo, semelhante ao awk do BSD, que também foi relatado como travado, se você quiser tentar isso. Observe que travar é geralmente o primeiro passo para encontrar um bug explorável. Esta resposta está promovendo códigos inseguros.

2
Nota aos usuários - original-awknão é padrão e não é recomendado #
Steven Penny

Uma alternativa para o primeiro snippet de código pode ser awk NF=100 OFS='=' <<< ""(using bashand gawk) #
1119

4

Eu acho que o objetivo original da pergunta era fazer isso apenas com os comandos internos do shell. Então, forloops e printfs seria legítimo, enquanto rep, perle também jotabaixo não faria. Ainda assim, o seguinte comando

jot -s "/" -b "\\" $((COLUMNS/2))

por exemplo, imprime uma linha de janelas inteira \/\/\/\/\/\/\/\/\/\/\/\/


2
Bem feito; isso funciona bem mesmo com altas contagens de repetição (enquanto também suporta seqüências de caracteres múltiplos). Para melhor ilustrar a abordagem, aqui é o equivalente a ordem do OP: jot -s '' -b '=' 100. A ressalva é que, embora as plataformas semelhantes a BSD, incluindo OSX, venham jot, as distribuições Linux não .
usar o seguinte comando

1
Obrigado, eu gosto mais do seu uso de -s. Eu mudei meus scripts.
Stefan Ludwig

Em sistemas recentes baseados no Debian , apt install athena-jotseria fornecido jot.
agc

4

Como outros já disseram, na expansão do bash brace precede a expansão do parâmetro , portanto, os intervalos podem conter apenas literais. e forneça soluções limpas, mas não totalmente portáteis de um sistema para outro, mesmo se você estiver usando o mesmo shell em cada um. (Embora esteja cada vez mais disponível; por exemplo, no FreeBSD 9.3 e superior .) E outras formas de indireção sempre funcionam, mas são um tanto deselegantes.{m,n}seqjotseqeval

Felizmente, o bash suporta o estilo C para loops (apenas com expressões aritméticas). Então, aqui está uma maneira concisa de "pure bash":

repecho() { for ((i=0; i<$1; ++i)); do echo -n "$2"; done; echo; }

Isso leva o número de repetições como o primeiro argumento e a sequência a ser repetida (que pode ser um caractere único, como na descrição do problema) como o segundo argumento. repecho 7 bsaídas bbbbbbb(terminadas por uma nova linha).

Dennis Williamson deu essencialmente essa solução há quatro anos em sua excelente resposta para Criando sequência de caracteres repetidos no shell script . Meu corpo da função difere um pouco do código lá:

  • Como o foco aqui é repetir um único caractere e o shell é bash, provavelmente é seguro usá-lo em echovez de printf. E li a descrição do problema nesta pergunta como expressando uma preferência para imprimir echo. A definição da função acima funciona no bash e no ksh93 . Embora printfseja mais portátil (e geralmente deva ser usado para esse tipo de coisa), echoa sintaxe é indiscutivelmente mais legível.

    Os echocomponentes de alguns shells interpretam -por si só como uma opção - mesmo que o significado usual de -, usar stdin para entrada, seja sem sentido echo. O zsh faz isso. E definitivamente existem echos que não reconhecem -n, pois isso não é padrão . (Muitas conchas no estilo Bourne não aceitam o estilo C para loops, portanto, seu echocomportamento não precisa ser considerado ..)

  • Aqui, a tarefa é imprimir a sequência; , era para atribuí-lo a uma variável.

Se $né o número desejado de repetições e você não precisa reutilizá-lo, e deseja algo ainda mais curto:

while ((n--)); do echo -n "$s"; done; echo

ndeve ser uma variável - dessa maneira não funciona com parâmetros posicionais. $sé o texto a ser repetido.


2
Evite fortemente fazer versões de loop. printf "%100s" | tr ' ' '='é ótimo.
Ocodo

Boas informações de base e elogios por empacotar a funcionalidade como uma função, que também funciona zsh, aliás. A abordagem echo-in-a-loop funciona bem para contagens menores de repetição, mas para as maiores existem alternativas compatíveis com POSIX baseadas em utilitários , conforme evidenciado pelo comentário do @ Slomojo.
precisa saber é o seguinte

Adicionando parênteses em torno de suas conservas de loop mais curtos o valor de n, sem afetar os ecos:(while ((n--)); do echo -n "$s"; done; echo)

use printf em vez de eco! é muito mais portátil (o echo -n pode funcionar apenas em alguns sistemas). veja unix.stackexchange.com/questions/65803/… (uma das respostas impressionantes de Stephane Chazelas)
Olivier Dulac

@OlivierDulac A questão aqui é sobre o bash. Não importa qual sistema operacional você esteja executando, se estiver usando o bash , o bash possui um echosuporte interno -n. O espírito do que você está dizendo é absolutamente correto. printfquase sempre deve ser preferido echo, pelo menos em uso não interativo. Mas não acho que seja inapropriado ou enganoso dar uma echoresposta a uma pergunta que pediu uma e que forneceu informações suficientes para saber que funcionaria . Observe também que o suporte para ((n--))(sem a $) não é garantido pelo POSIX.
Eliah Kagan em 14/02

4

Python é onipresente e funciona da mesma maneira em todos os lugares.

python -c "import sys; print('*' * int(sys.argv[1]))" "=" 100

Caractere e contagem são passados ​​como parâmetros separados.


Eu acho que essa era a intenção aquipython -c "import sys; print(sys.argv[1] * int(sys.argv[2]))" "=" 100
gazhay 21/02

@loevborg não é tão exagerado?
Sapphire_Brick

3

No bash 3.0 ou superior

for i in {1..100};do echo -n =;done

3

Outra forma de repetir uma sequência arbitrária n vezes:

Prós:

  • Funciona com shell POSIX.
  • A saída pode ser atribuída a uma variável.
  • Repete qualquer string.
  • Muito rápido, mesmo com repetições muito grandes.

Contras:

  • Requer o yescomando do Gnu Core Utils .
#!/usr/bin/sh
to_repeat='='
repeat_count=80
yes "$to_repeat" | tr -d '\n' | head -c "$repeat_count"

Com um terminal ANSI e caracteres US-ASCII para repetir. Você pode usar uma sequência de escape ANSI CSI. É a maneira mais rápida de repetir um personagem.

#!/usr/bin/env bash

char='='
repeat_count=80
printf '%c\e[%db' "$char" "$repeat_count"

Ou estaticamente:

Imprima uma linha de 80 vezes =:

printf '=\e[80b\n'

Limitações:

  • Nem todos os terminais compreendem a repeat_charsequência ANSI CSI.
  • Somente caracteres US-ASCII ou ISO de byte único podem ser repetidos.
  • Repita as paradas na última coluna, para que você possa usar um valor grande para preencher uma linha inteira, independentemente da largura do terminal.
  • A repetição é apenas para exibição. Capturar a saída em uma variável de shell não expandirá a repeat_charsequência ANSI CSI no caractere repetido.

1
Nota secundária - REP (CSI b) deve ser contornado normalmente se o terminal estiver no modo de envolvimento.
jerch 2/01

3

Aqui está o que eu uso para imprimir uma linha de caracteres na tela no Linux (com base na largura do terminal / tela)

Imprimir "=" na tela:

printf '=%.0s' $(seq 1 $(tput cols))

Explicação:

Imprima um sinal de igual quantas vezes a sequência fornecida:

printf '=%.0s' #sequence

Use a saída de um comando (esse é um recurso do bash chamado Substituição de Comando):

$(example_command)

Dê uma sequência, usei 1 a 20 como exemplo. No comando final, o comando tput é usado em vez de 20:

seq 1 20

Forneça o número de colunas atualmente usadas no terminal:

tput cols


2
repeat() {
    # $1=number of patterns to repeat
    # $2=pattern
    printf -v "TEMP" '%*s' "$1"
    echo ${TEMP// /$2}
}

2

O mais simples é usar esta linha em csh / tcsh:

printf "%50s\n" '' | tr '[:blank:]' '[=]'


2

Uma alternativa mais elegante à solução Python proposta poderia ser:

python -c 'print "="*(1000)'

1

Caso deseje repetir um caractere n vezes, sendo um número VARIÁVEL de vezes, dependendo, digamos, do comprimento de uma string que você possa fazer:

#!/bin/bash
vari='AB'
n=$(expr 10 - length $vari)
echo 'vari equals.............................: '$vari
echo 'Up to 10 positions I must fill with.....: '$n' equal signs'
echo $vari$(perl -E 'say "=" x '$n)

Exibe:

vari equals.............................: AB  
Up to 10 positions I must fill with.....: 8 equal signs  
AB========  

lengthnão vai funcionar expr, você provavelmente quis dizer n=$(expr 10 - ${#vari}); no entanto, é mais simples e mais eficiente usar a expansão aritmética do Bash: n=$(( 10 - ${#vari} )). Além disso, no centro da sua resposta está a própria abordagem Perl para a qual o OP está procurando uma alternativa do Bash .
precisa saber é o seguinte

1

Esta é a versão mais longa do que Eliah Kagan estava defendendo:

while [ $(( i-- )) -gt 0 ]; do echo -n "  "; done

Claro que você também pode usar o printf para isso, mas não do meu jeito:

printf "%$(( i*2 ))s"

Esta versão é compatível com Dash:

until [ $(( i=i-1 )) -lt 0 ]; do echo -n "  "; done

sendo eu o número inicial.


No bash e com um n: positivo while (( i-- )); do echo -n " "; done.

1
function repeatString()
{
    local -r string="${1}"
    local -r numberToRepeat="${2}"

    if [[ "${string}" != '' && "${numberToRepeat}" =~ ^[1-9][0-9]*$ ]]
    then
        local -r result="$(printf "%${numberToRepeat}s")"
        echo -e "${result// /${string}}"
    fi
}

Execuções de amostra

$ repeatString 'a1' 10 
a1a1a1a1a1a1a1a1a1a1

$ repeatString 'a1' 0 

$ repeatString '' 10 

Referência lib em: https://github.com/gdbtek/linux-cookbooks/blob/master/libraries/util.bash


1

Como eu poderia fazer isso com eco?

Você pode fazer isso com echose o echofor seguido por sed:

echo | sed -r ':a s/^(.*)$/=\1/; /^={100}$/q; ba'

Na verdade, isso echoé desnecessário lá.


1

Minha resposta é um pouco mais complicada e, provavelmente, não é perfeita, mas para aqueles que desejam gerar grandes números, consegui fazer cerca de 10 milhões em 3 segundos.

repeatString(){
    # argument 1: The string to print
    # argument 2: The number of times to print
    stringToPrint=$1
    length=$2

    # Find the largest integer value of x in 2^x=(number of times to repeat) using logarithms
    power=`echo "l(${length})/l(2)" | bc -l`
    power=`echo "scale=0; ${power}/1" | bc`

    # Get the difference between the length and 2^x
    diff=`echo "${length} - 2^${power}" | bc`

    # Double the string length to the power of x
    for i in `seq "${power}"`; do 
        stringToPrint="${stringToPrint}${stringToPrint}"
    done

    #Since we know that the string is now at least bigger than half the total, grab however many more we need and add it to the string.
    stringToPrint="${stringToPrint}${stringToPrint:0:${diff}}"
    echo ${stringToPrint}
}

1

O mais simples é usar este one-liner no bash:

seq 10 | xargs -n 1 | xargs -I {} echo -n  ===\>;echo


1

Outra opção é usar o GNU seq e remover todos os números e novas linhas que ele gerar:

seq -f'#%.0f' 100 | tr -d '\n0123456789'

Este comando imprime o #caractere 100 vezes.


1

A maioria das soluções existentes depende do {1..10}suporte à sintaxe do shell, que é bash- e zsh- específico, e não funciona no tcshOpenBSD kshe na maioria dos não-bash sh.

O seguinte deve funcionar no OS X e em todos os sistemas * BSD em qualquer shell; de fato, ele pode ser usado para gerar uma matriz inteira de vários tipos de espaço decorativo:

$ printf '=%.0s' `jot 64` | fold -16
================
================
================
================$ 

Infelizmente, não temos uma nova linha à direita; que pode ser corrigido com um extra printf '\n'após a dobra:

$ printf "=%.0s" `jot 64` | fold -16 ; printf "\n"
================
================
================
================
$ 

Referências:


Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.