¿Cómo puedo repetir un personaje en bash?

¿Cómo podría hacer esto con echo ?

 perl -E 'say "=" x 100' 

Puedes usar:

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

Cómo funciona esto:

Bash expande {1..100} por lo que el comando se convierte en:

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

He configurado el formato de printf en =%.0s que significa que siempre imprimirá un único = no importa el argumento que se le dé. Por lo tanto, imprime 100 = s.

No es una manera fácil. Pero por ejemplo:

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

O tal vez una forma de conformidad estándar:

 printf %100s |tr " " "=" 

También hay un tput rep , pero en cuanto a mis terminales disponibles (xterm y linux) no parecen apoyarlo 🙂

Hay más de una forma de hacerlo.

Usando un bucle:

  • La expansión de llaves se puede usar con literales enteros:

     for i in {1..100}; do echo -n =; done 
  • Un ciclo tipo C permite el uso de variables:

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

Uso de printf incorporado:

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

Especificar una precisión aquí trunca la cadena para que se ajuste al ancho especificado ( 0 ). Como printf reutiliza la cadena de formato para consumir todos los argumentos, esto simplemente imprime "=" 100 veces.

Usando head ( printf , etc) y tr :

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

Consejo del sombrero a @ gniourf_gniourf por su entrada.

Nota: Esta respuesta no responde a la pregunta original, sino que complementa las respuestas útiles existentes al comparar el rendimiento .

Las soluciones solo se comparan en términos de velocidad de ejecución : no se tienen en cuenta los requisitos de memoria (varían de una solución a otra y pueden ser importantes con recuentos de repeticiones grandes).

Resumen:

  • Si su recuento de repeticiones es pequeño , digamos que hasta alrededor de 100, vale la pena ir con las soluciones solo de Bash , ya que el costo de inicio de los servicios externos es importante, especialmente el de Perl.
    • Pragmáticamente hablando, sin embargo, si solo necesita una instancia de repetición de caracteres, todas las soluciones existentes pueden estar bien.
  • Con recuentos de repeticiones grandes , use utilidades externas , ya que serán mucho más rápidas.
    • En particular, evite el reemplazo de subcadena global de Bash con cadenas grandes
      (por ejemplo, ${var// /=} ), ya que es prohibitivamente lento.

Los siguientes son los tiempos tomados en un iMac de finales de 2012 con una CPU Intel Core i5 de 3.2 GHz y una unidad Fusion, ejecutando OSX 10.10.4 y bash 3.2.57, y son el promedio de 1000 ejecuciones.

Las entradas son:

  • enumerados en orden ascendente de la duración de la ejecución (el más rápido primero)
  • prefijado con:
    • M … una solución potencialmente multi- personaje
    • S … una solución de un solo carácter
    • P … una solución compatible con POSIX
  • seguido de una breve descripción de la solución
  • sufijo con el nombre del autor de la respuesta de origen

  • Pequeño número de repeticiones: 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 
  • Las soluciones de Bash solo lideran el paquete, pero solo con un conteo repetido tan pequeño. (vea abajo).
  • El costo inicial de los servicios externos sí importa aquí, especialmente el de Perl. Si debe llamar esto en un bucle, con pequeños recuentos de repetición en cada iteración, evite las soluciones de utilidad múltiple, awk y perl .

  • Gran cantidad de repeticiones: 1000000 (1 millón)
 [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 
  • La solución Perl de la pregunta es, con mucho, la más rápida.
  • El reemplazo de cadena global de Bash ( ${foo// /=} ) es inexplicablemente lento con cadenas de caracteres grandes, y se ha eliminado de la ejecución (tomó alrededor de 50 minutos (!) En Bash 4.3.30, e incluso más tiempo en Bash 3.2.57 – Nunca esperé a que terminara).
  • Los bucles Bash son lentos, y los bucles aritméticos ( (( i= 0; ... )) ) son más lentos que los {1..n} refuerzo ( {1..n} ), aunque los bucles aritméticos son más eficientes en cuanto a la memoria.
  • awk refiere a BSD awk (como también se encuentra en OSX) – es notablemente más lento que gawk (GNU Awk) y especialmente mawk .
  • Tenga en cuenta que con cuentas grandes y multi-char. cadenas, consumo de memoria puede convertirse en una consideración – los enfoques difieren en ese sentido.

Aquí está el script Bash ( testrepeat ) que produjo lo anterior. Toma 2 argumentos:

  • el recuento repetido del personaje
  • opcionalmente, el número de ejecuciones de prueba para realizar y calcular el tiempo promedio de

En otras palabras: los tiempos anteriores se obtuvieron con testrepeat 100 1000 y testrepeat 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:  []} # 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; eg, 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"$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 -vt "%${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 

Acabo de encontrar una forma seriamente fácil de hacer esto usando seq:

ACTUALIZACIÓN: Esto funciona en el BSD seq que viene con OS X. YMMV con otras versiones

 seq -f "#" -s '' 10 

Se imprimirá ‘#’ 10 veces, así:

 ########## 
  • -f "#" establece la cadena de formato para ignorar los números y simplemente imprime # para cada uno.
  • -s '' establece el separador en una cadena vacía para eliminar las líneas nuevas que se inserta seq entre cada número
  • Los espacios después de -f y -s parecen ser importantes.

EDITAR: Aquí está en una función práctica …

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

Que puedes llamar así …

 repeat "#" 10 

NOTA: si repite # , ¡las citas son importantes!

Aquí hay dos formas interesantes:

 ubuntu @ ubuntu: ~ $ yes = |  cabeza -10 |  pegar -s -d '' -
 ==========
 ubuntu @ ubuntu: ~ $ yes = |  cabeza -10 |  tr -d "\ n"
 ========== ubuntu @ ubuntu: ~ $ 

Tenga en cuenta que estos dos son sutilmente diferentes: el método de paste termina en una nueva línea. El método tr no.

No hay una manera simple. Evite bucles utilizando printf y sustitución.

 str=$(printf "%40s") echo ${str// /rep} # echoes "rep" 40 times. 
 #!/usr/bin/awk -f BEGIN { OFS = "=" NF = 100 print } 

O

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

Ejemplo

Supongo que el propósito original de la pregunta era hacer esto solo con los comandos integrados del shell. Entonces, for loops y printf s sería legítimo, mientras que rep , perl , y también jot abajo no lo serían. Aún así, el siguiente comando

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

por ejemplo, imprime una línea de \/\/\/\/\/\/\/\/\/\/\/\/ toda la ventana

Si desea compatibilidad con POSIX y consistencia en diferentes implementaciones de echo y printf , y / o shells que no sean 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) 

… producirá el mismo resultado que perl -E 'say "=" x 100' casi en todas partes.

Como han dicho otros, en la corrección de bash, la expansión precede a la expansión de parámetros , por lo que { m , n } rangos solo pueden contener literales. seq y jot proporcionan soluciones limpias, pero no son totalmente portátiles de un sistema a otro, incluso si está utilizando el mismo shell en cada uno. (Aunque cada vez más seq disponible, por ejemplo, en FreeBSD 9.3 y superior ) eval y otras formas de indirección siempre funcionan, pero son algo poco elegantes.

Afortunadamente, bash admite C-style para bucles (solo con expresiones aritméticas). Así que aquí hay una forma concisa de “puro bash”:

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

Esto toma el número de repeticiones como el primer argumento y la cadena que se repetirá (que puede ser un solo carácter, como en la descripción del problema) como el segundo argumento. salidas repecho 7 b bbbbbbb (terminadas por una nueva línea).

Dennis Williamson dio esta solución en esencia hace cuatro años en su excelente respuesta a la creación de cadena de caracteres repetidos en el guión de shell . Mi cuerpo de función difiere ligeramente del código allí:

  • Como el objective aquí es repetir un solo carácter y el shell es bash, probablemente sea seguro usar echo lugar de printf . Y leí la descripción del problema en esta pregunta como express una preferencia para imprimir con echo . La definición de función anterior funciona en bash y ksh93 . Aunque printf es más portable (y debería usarse generalmente para este tipo de cosas), la syntax de echo es posiblemente más legible.

    Las interpretaciones internas de echo algunas shells interpretan - por sí mismas como una opción, aunque el significado habitual de - , usar stdin para la entrada, no tiene sentido para echo . zsh hace esto. Y definitivamente existen echo que no reconocen -n , ya que no es estándar . (Muchos shells de estilo Bourne no aceptan el estilo C para bucles en absoluto, por lo tanto, no es necesario considerar su comportamiento de echo ).

  • Aquí la tarea es imprimir la secuencia; allí , fue para asignarlo a una variable.

Si $n es el número deseado de repeticiones y no tiene que reutilizarlo, y desea algo aún más corto:

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

n debe ser una variable, de esta manera no funciona con parámetros posicionales. $s es el texto que se repetirá.

En bash 3.0 o superior

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

Una forma pura de Bash sin eval , sin subcapas, sin herramientas externas, sin expansiones de llaves (es decir, puede tener el número para repetir en una variable):

Si le dan una variable n que se expande a un número (no negativo) y un pattern variable, por ejemplo,

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

Puedes hacer una función con esto:

 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}" } 

Con este conjunto:

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

Para este pequeño truco, usamos printf bastante con:

  • -v varname : en lugar de imprimir a la salida estándar, printf colocará el contenido de la cadena formateada en varname variable.
  • ‘% * s’: printf usará el argumento para imprimir el número de espacios correspondiente. Por ejemplo, printf '%*s' 42 imprimirá 42 espacios.
  • Finalmente, cuando tenemos el número deseado de espacios en nuestra variable, usamos una expansión de parámetros para reemplazar todos los espacios por nuestro patrón: ${var// /$pattern} se expandirá a la expansión de var con todos los espacios reemplazados por la expansión de $pattern .

También puede deshacerse de la variable tmp en la función de repeat mediante el uso de expansión indirecta:

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

En caso de que quiera repetir un carácter n veces, na es VARIABLE número de veces dependiendo de, digamos, la longitud de una cadena que puede hacer:

 #!/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) 

Muestra:

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

Esta es la versión más larga de lo que Eliah Kagan estaba propugnando:

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

Por supuesto, también puedes usar printf para eso, pero no realmente para mi gusto:

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

Esta versión es compatible con Dash:

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

siendo yo el número inicial.

Python es omnipresente y funciona igual en todos lados.

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

El carácter y el recuento se pasan como parámetros separados.

 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 } 

Se ejecuta la muestra

 $ repeatString 'a1' 10 a1a1a1a1a1a1a1a1a1a1 $ repeatString 'a1' 0 $ repeatString '' 10 

Referencia lib en: https://github.com/gdbtek/linux-cookbooks/blob/master/libraries/util.bash