¿Cómo puedo repetir un personaje en Bash?

240

¿Cómo podría hacer esto echo?

perl -E 'say "=" x 100'
sid_com
fuente
Lamentablemente esto no es Bash.
solidsnack
1
no con eco, sino sobre el mismo tema ruby -e 'puts "=" * 100'opython -c 'print "=" * 100'
Evgeny
1
Gran pregunta Muy buenas respuestas. He usado una de las respuestas en un trabajo real aquí, que publicaré como ejemplo: github.com/drbeco/oldfiles/blob/master/oldfiles (usado printfcon seq)svrb=`printf '%.sv' $(seq $vrb)`
Dr Beco
Una solución genérica para imprimir lo que sea (1 o más caracteres, incluso las nuevas líneas): Repeat_this () {i = 1; mientras que ["$ i" -le "$ 2"]; hacer printf "% s" "$ 1"; i = $ (($ i + 1)); hecho ; printf '\ n';}. Use así: Repita_este "algo" Número_de_repeticiones. Por ejemplo, para mostrar la repetición de 5 veces algo que incluye 3 líneas nuevas: Repita_este "$ (printf '\ n \ n \ neste')" 5. La impresión final '\ n' puede ser eliminada (¡pero la puse para crear archivos de texto, y esos necesitan una nueva línea como su último personaje!)
Olivier Dulac

Respuestas:

396

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, lo =%.0sque significa que siempre imprimirá uno =sin importar el argumento que se le dé. Por lo tanto, imprime 100 =s.

dogbane
fuente
14
Gran solución que funciona razonablemente bien incluso con grandes recuentos repetidos. Aquí hay un contenedor de funciones con el que puede invocar repl = 100, por ejemplo ( evalse requiere truco, desafortunadamente, para basar la expansión de repl() { printf "$1"'%.s' $(eval "echo {1.."$(($2))"}"); }
llaves
77
¿Es posible establecer el límite superior usando una var? Lo he intentado y no puedo hacerlo funcionar.
Mike Purcell
70
No puede usar variables dentro de la expansión de llaves. Utilice en su seqlugar, por ejemplo $(seq 1 $limit).
dogbane
11
Si funcionaliza esto, es mejor reorganizarlo $s%.0spara %.0s$sque los guiones causen un printferror.
KomodoDave
55
Esto me hizo notar un comportamiento de Bash printf: continúa aplicando la cadena de formato hasta que no quedan argumentos. ¡Asumí que procesó la cadena de formato solo una vez!
Jeenu
89

No hay 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 admitirlo :)


fuente
3
Tenga en cuenta que la primera opción con seq imprime uno menos que el número dado, por lo que ese ejemplo imprimirá 99 =caracteres.
Camilo Martin
13
printf tres la única solución POSIX porque seq, yesy {1..3}no son POSIX.
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
2
Para repetir una cadena en lugar de un solo carácter: printf %100s | sed 's/ /abc/g'- genera 'abcabcabc ...'
John Rix
3
+1 por no usar bucles y solo un comando externo ( tr). También podría extenderlo a algo así printf "%${COLUMNS}s\n" | tr " " "=".
musiphil
2
@ mklement0 Bueno, esperaba que estuvieras contando la última línea nueva por error wc. La única conclusión que puedo sacar de esto es " seqno se debe usar".
Camilo Martin
51

Punta del sombrero a @ gniourf_gniourf por su aporte.

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

Las soluciones se comparan solo en términos de velocidad de ejecución : los requisitos de memoria no se tienen en cuenta (varían de una solución a otra y pueden tener un gran número de repeticiones).

Resumen:

  • Si su recuento de repeticiones es pequeño , digamos hasta alrededor de 100, vale la pena ir con las soluciones de solo Bash , ya que el costo inicial de las utilidades externas es importante, especialmente el de Perl.
    • Hablando pragmáticamente, sin embargo, si solo necesita una instancia de caracteres repetidos, todas las soluciones existentes pueden estar bien.
  • Con grandes recuentos repetidos , 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 tiempos tomados en un iMac de finales de 2012 con una CPU Intel Core i5 de 3.2 GHz y una unidad Fusion, con OSX 10.10.4 y bash 3.2.57, y son el promedio de 1000 ejecuciones.

Las entradas son:

  • enumerado en orden ascendente de duración de la ejecución (el más rápido primero)
  • con el prefijo:
    • M... una solución potencialmente de múltiples caracteres
    • S... una solución de un solo personaje
    • 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 original.

  • Cuenta de repetición pequeña: 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 solo Bash lideran el paquete, ¡pero solo con un recuento de repetición tan pequeño! (vea abajo).
  • El costo inicial de las utilidades externas sí importa aquí, especialmente el de Perl. Si debe llamar a esto en un bucle, con pequeños recuentos de repeticiones en cada iteración, evite las soluciones awky la utilidad múltiple perl.

  • Gran número 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 grandes, y ha sido eliminado de la ejecución (tomó alrededor de 50 minutos (!) En Bash 4.3.30, e incluso más en Bash 3.2.57 - Nunca esperé para terminar).
  • Los bucles bash son lentos y los bucles aritméticos ( (( i= 0; ... ))) son más lentos que los expandidos con llaves ( {1..n}), aunque los bucles aritméticos son más eficientes en cuanto a memoria.
  • awkse refiere a BSD awk (como también se encuentra en OSX): es notablemente más lento que gawk(GNU Awk) y especialmentemawk .
  • Tenga en cuenta que con grandes recuentos y multi-char. cadenas, el 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. Se necesitan 2 argumentos:

  • el personaje repite cuenta
  • 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 1000ytestrepeat 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
mklement0
fuente
Es interesante ver la comparación de temporización, pero creo que en muchos programas la salida está almacenada en búfer, por lo que su sincronización puede modificarse si se desactiva el almacenamiento en búfer.
Sergiy Kolodyazhnyy
In order to use brace expansion with a variable, we must use `eval`👍
pyb
2
Entonces, la solución perl (sid_com) es básicamente la más rápida ... una vez que se alcanza la sobrecarga inicial de iniciar perl. (va de 59 ms para una pequeña repetición a 67 ms para un millón de repeticiones ... por lo que la bifurcación de perl tomó aproximadamente 59 ms en su sistema)
Olivier Dulac
46

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 bucle tipo C permite el uso de variables:

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

Usando el printfincorporado:

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

Especificar una precisión aquí trunca la cadena para que se ajuste al ancho especificado ( 0). Como printfreutiliza 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 " " "="
Eugene Yarmash
fuente
2
++ para la solución head/ tr, que funciona bien incluso con recuentos altos de repetición (pequeña advertencia: head -cno es compatible con POSIX, pero BSD y GNU lo headimplementan); Si bien las otras dos soluciones serán lentas en ese caso, también tienen la ventaja de trabajar con cadenas de caracteres múltiples .
mklement0
1
El uso yesy head- útil si se desea un cierto número de saltos de línea: yes "" | head -n 100. trpuede hacer que imprima cualquier carácter:yes "" | head -n 100 | tr "\n" "="; echo
loxaxs
Algo sorprendente: dd if=/dev/zero count=1 bs=100000000 | tr '\0' '=' >/dev/nulles significativamente más lento que la head -c100000000 < /dev/zero | tr '\0' '=' >/dev/nullversión. Por supuesto, debe usar un tamaño de bloque de 100M + para medir la diferencia de tiempo razonablemente. 100M bytes toman 1.7 sy 1 s con las dos versiones respectivas mostradas. Me quité el tr y lo descargué /dev/nully obtuve 0.287 s para la headversión y 0.675 s para la ddversión por mil millones 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
31

Acabo de encontrar una manera realmente fácil de hacer esto usando seq:

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

seq  -f "#" -s '' 10

Imprimirá '#' 10 veces, así:

##########
  • -f "#"establece la cadena de formato para ignorar los números y solo imprimir #para cada uno.
  • -s '' establece el separador en una cadena vacía para eliminar las nuevas líneas que se insertan entre cada número
  • Los espacios posteriores -fy -sparecen ser importantes.

EDITAR: Aquí está en una función útil ...

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

A lo que puedes llamar así ...

repeat "#" 10

NOTA: Si estás repitiendo #, ¡las citas son importantes!

Sam Salisbury
fuente
77
Esto me da seq: format ‘#’ has no % directive. seqes para números, no cadenas. Ver gnu.org/software/coreutils/manual/html_node/seq-invocation.html
John B
Ah, entonces estaba usando la versión BSD de seq que se encuentra en OS X. Actualizaré la respuesta. ¿Qué versión está utilizando?
Sam Salisbury
Estoy usando seq de GNU coreutils.
John B
1
@JohnB: BSD seqse está reutilizando inteligentemente aquí para replicar cadenas : la cadena de formato que se pasa a -f, normalmente utilizada para formatear los números que se generan, contiene solo la cadena para replicar aquí, de modo que la salida contiene copias de esa cadena solamente. Desafortunadamente, GNU seqinsiste en la presencia de un formato de número en la cadena de formato, que es el error que está viendo.
mklement0
1
Bien hecho; También funciona con cadenas de múltiples caracteres. Utilice "$1"(comillas dobles), para que también pueda pasar caracteres como '*'y cadenas con espacios en blanco incrustados. Finalmente, si desea poder usarlo %, debe duplicarlo (de lo contrario seq, pensará que es parte de una especificación de formato como %f); usando "${1//%/%%}"se encargaría de eso. Como (como mencionas) estás usando BSD seq , esto funcionará en sistemas operativos similares a BSD en general (por ejemplo, FreeBSD); por el contrario, no funcionará en Linux , donde se usa GNU seq .
mklement0
18

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 pastemétodo termina en una nueva línea. El trmétodo no lo hace.

Trauma digital
fuente
1
Bien hecho; tenga en cuenta que BSD paste requiere inexplicablemente -d '\0'para especificar un delimitador vacío, y falla con -d '': -d '\0'debería funcionar con todas las pasteimplementaciones compatibles con POSIX y, de hecho, también funciona con GNU paste .
mklement0
Similar en espíritu, con menos herramientas fuera de borda:yes | mapfile -n 100 -C 'printf = \#' -c 1
Obispo
@bishop: Si bien su comando de hecho crea una subshell menos, aún es más lenta para conteos de repetición más altos, y para conteos de repetición más bajos, la diferencia probablemente no importa; el umbral exacto probablemente depende tanto del hardware como del sistema operativo, por ejemplo, en mi máquina OSX 10.11.5 esta respuesta ya es más rápida en 500; tratar time yes = | head -500 | paste -s -d '\0' -; time yes | mapfile -n 500 -C 'printf = \#' -c 1. Sin embargo, lo que es más importante: si está utilizando de printftodos modos, también puede printf '%.s=' $(seq 500)
optar
13

No hay manera simple. Evite el uso de bucles printfy la sustitución.

str=$(printf "%40s")
echo ${str// /rep}
# echoes "rep" 40 times.
Tim
fuente
2
Agradable, pero solo funciona razonablemente con pequeños recuentos repetidos. Aquí hay un contenedor de funciones que se puede invocar como repl = 100, por ejemplo (no \nrepl() { local ts=$(printf "%${2}s"); printf %s "${ts// /$1}"; }
genera
1
@ mklement0 ¡Es amable de su parte proporcionar versiones funcionales de ambas soluciones, +1 en ambas!
Camilo Martin
8

Si quieres POSIX-cumplimiento y la coherencia entre las diferentes implementaciones de echoy printf, y / o conchas que no sea sólo 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á la misma salida que perl -E 'say "=" x 100'casi en todas partes.

Geoff Nixon
fuente
1
El problema es que seqno es una utilidad POSIX (aunque los sistemas BSD y Linux tienen implementaciones de la misma): en su lugar, puede hacer aritmética de shell POSIX con un whilebucle, como en la respuesta de @ Xennex81 (con printf "=", como sugiere correctamente, en lugar de echo -n).
mklement0
1
Vaya, tienes toda la razón. A veces me pasan cosas así, ya que ese estándar no tiene ningún sentido. cales POSIX seqno es. De todos modos, en lugar de reescribir la respuesta con un ciclo while (como dices, eso ya está en otras respuestas) agregaré una función RYO. Más educativo de esa manera ;-).
Geoff Nixon
8

La pregunta era sobre cómo hacerlo con echo:

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

Esto hará exactamente lo mismo perl -E 'say "=" x 100'pero echosolo con .

manifestante
fuente
Ahora eso es inusual, si no combina espacios de espacio extra en él ... o lo limpia usando: echo -e $ _ {1..100} '\ b =' | col
anthony
1
Mala idea. Esto fallará si $_1, $_2o cualquier otra de las cien variables tienen valores.
John Kugelman el
@JohnKugelman echo $ (set -; eval echo -e \ $ {{1..100}} '\\ b =')
mug896
Esto es asqueroso . Me encanta: D
dimo414
6

Una manera 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 nque se expande a un número (no negativo) y una variable pattern, 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 estamos usando printfbastante con:

  • -v varname: en lugar de imprimir a la salida estándar, printfcolocará el contenido de la cadena formateada en variable varname.
  • '% * s': printfusará el argumento para imprimir el número correspondiente de espacios. Por ejemplo, printf '%*s' 42imprimirá 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 varcon todos los espacios reemplazados por la expansión de $pattern.

También puede deshacerse de la tmpvariable en la repeatfunción utilizando la 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}"
}
gniourf_gniourf
fuente
Variación interesante para pasar el nombre de la variable. Si bien esta solución está bien para los recuentos repetidos de hasta alrededor de 1,000 (y, por lo tanto, probablemente esté bien para la mayoría de las aplicaciones de la vida real, si tuviera que adivinar), se vuelve muy lenta para los recuentos más altos (ver a continuación comentario).
mklement0
Parece que bashlas operaciones de reemplazo de cadenas globales en el contexto de la expansión de parámetros ( ${var//old/new}) son particularmente lentas: terriblemente lento en bash 3.2.57y lento en bash 4.3.30, al menos en mi sistema OSX 10.10.3 en una máquina Intel Core i5 de 3.2 Ghz: con un recuento de 1,000, las cosas son lentas ( 3.2.57) / rápidas ( 4.3.30): 0.1 / 0.004 segundos. Aumentar la cuenta a 10,000 produce números sorprendentemente diferentes: repeat 10000 = vartoma alrededor de 80 segundos (!) En bash 3.2.57, y alrededor de 0.3 segundos en bash 4.3.30(mucho más rápido que encendido 3.2.57, pero aún lento).
mklement0
6
#!/usr/bin/awk -f
BEGIN {
  OFS = "="
  NF = 100
  print
}

O

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

Ejemplo

Steven Penny
fuente
3
Bien hecho; Esto es compatible con POSIX y razonablemente rápido incluso con un alto número de repeticiones, al tiempo que admite cadenas de caracteres múltiples. Aquí está la versión de la shell: awk 'BEGIN { while (c++ < 100) printf "=" }'. Envuelta en una función de shell parametrizado (invoke como repeat 100 =, por ejemplo): repeat() { awk -v count="$1" -v txt=".$2" 'BEGIN { txt=substr(txt, 2); while (i++ < count) printf txt }'; }. (El .prefijo ficticio char y la substrllamada complementaria son necesarios para evitar un error en BSD awk, donde pasar un valor variable que comienza con =rompe el comando.)
mklement0
1
La NF = 100solución es muy inteligente (aunque para obtener 100 =, debe usar NF = 101). Las advertencias son que se estrella BSD awk(pero es muy rápido con gawky aún más rápido con mawk), y que se analizan en POSIX ni asignar a NF, ni el uso de los campos en BEGINlos bloques. Puede hacer que funcione también en BSD awkcon un ligero ajuste: awk 'BEGIN { OFS = "="; $101=""; print }'(pero curiosamente, en BSD awkeso no es más rápido que la solución de bucle). Como solución shell parametrizada: repeat() { awk -v count="$1" -v txt=".$2" 'BEGIN { OFS=substr(txt, 2); $(count+1)=""; print }'; }.
mklement0
Nota para los usuarios: el truco NF = 100 provoca un fallo de segmento en un awk anterior. El original-awkes el nombre bajo Linux del awk más antiguo similar al awk de BSD, que también se ha informado que se bloquea, si desea probar esto. Tenga en cuenta que el bloqueo suele ser el primer paso para encontrar un error explotable. Esta respuesta está promoviendo un código inseguro.
2
Nota para los usuarios - original-awkno es estándar y no se recomienda
Steven Penny
Una alternativa al primer fragmento de código puede ser awk NF=100 OFS='=' <<< ""(usando bashy gawk)
oliv
4

Supongo que el propósito original de la pregunta era hacer esto solo con los comandos integrados del shell. Así forbucles y printfs serían legítimos, mientras que rep, perly también jotmás adelante no lo haría. Aún así, el siguiente comando

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

por ejemplo, imprime una línea de \/\/\/\/\/\/\/\/\/\/\/\/

Stefan Ludwig
fuente
2
Bien hecho; esto funciona bien incluso con recuentos altos de repetición (a la vez que admite cadenas de varios caracteres). Para ilustrar mejor el enfoque, esto es el equivalente a la orden del OP: jot -s '' -b '=' 100. La advertencia es que si bien las plataformas tipo BSD, incluido OSX, vienen con jot, las distribuciones de Linux no .
mklement0
1
Gracias, me gusta su uso de -s '' aún mejor. He cambiado mis guiones.
Stefan Ludwig
En sistemas recientes basados ​​en Debian , apt install athena-jotproporcionaría jot.
agc
4

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

Afortunadamente, bash admite el estilo C para bucles (solo con expresiones aritméticas). Así que aquí hay una forma concisa de "puro golpe":

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. repecho 7 bsalidas bbbbbbb(terminadas por una nueva línea).

Dennis Williamson dio esencialmente esta solución hace cuatro años en su excelente respuesta a la creación de cadenas de caracteres repetidos en script de shell . El cuerpo de mi función difiere ligeramente del código allí:

  • Dado que el enfoque aquí está en repetir un solo carácter y el shell es bash, probablemente sea seguro usarlo en echolugar de hacerlo printf. Y leí la descripción del problema en esta pregunta como una preferencia por imprimir echo. La definición de función anterior funciona en bash y ksh93 . Aunque printfes más portátil (y generalmente debería usarse para este tipo de cosas), echola sintaxis de este es posiblemente más legible.

    Las echofunciones integradas de algunos shells se interpretan -por sí mismas como una opción, aunque el significado habitual de -utilizar stdin para la entrada no tiene sentido echo. zsh hace esto. Y definitivamente existen correos electrónicos echoque no reconocen -n, ya que no es estándar . (Muchos proyectiles de estilo Bourne no aceptan el estilo C para los bucles, por lo tanto, echono es necesario considerar su comportamiento ...)

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

Si $nes 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

ndebe ser una variable; de ​​esta manera no funciona con parámetros posicionales. $ses el texto a repetir.

Eliah Kagan
fuente
2
Evita hacer versiones de bucle. printf "%100s" | tr ' ' '='Es óptimo.
ocodo
Buena información de fondo y felicitaciones para empaquetar la funcionalidad como una función, que también funciona zsh, por cierto El enfoque echo-in-a-loop funciona bien para recuentos de repetición más pequeños, pero para los más grandes existen alternativas compatibles con POSIX basadas en utilidades , como lo demuestra el comentario de @ Slomojo.
mklement0
Agregar paréntesis alrededor de su ciclo más corto conserva el valor de n sin afectar los ecos:(while ((n--)); do echo -n "$s"; done; echo)
use printf en lugar de echo! es mucho más portátil (echo -n solo puede funcionar en algunos sistemas). ver unix.stackexchange.com/questions/65803/… (una de las increíbles respuestas de Stephane Chazelas)
Olivier Dulac
@OlivierDulac La pregunta aquí es sobre bash. No importa qué sistema operativo esté ejecutando, si está usando bash en él , bash tiene una función echointegrada que lo admite -n. El espíritu de lo que estás diciendo es absolutamente correcto. printfdebería preferirse casi siempre echo, al menos en uso no interactivo. Pero no creo que sea de ninguna manera inapropiado o engañoso dar una echorespuesta a una pregunta que hizo una y que proporcionó suficiente información para saber que funcionaría . Tenga en cuenta también que el soporte para ((n--))(sin a $) no está garantizado por POSIX.
Eliah Kagan
4

Python es omnipresente y funciona igual en todas partes.

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

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

loevborg
fuente
Creo que esta fue la intención aquípython -c "import sys; print(sys.argv[1] * int(sys.argv[2]))" "=" 100
gazhay
@loevborg no es tan exagerado?
Sapphire_Brick
3

En bash 3.0 o superior

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

Otro medio para repetir una cadena arbitraria n veces:

Pros:

  • Funciona con shell POSIX.
  • La salida se puede asignar a una variable.
  • Repite cualquier cadena.
  • Muy rápido incluso con repeticiones muy grandes.

Contras:

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

Con un terminal ANSI y caracteres US-ASCII para repetir. Puede usar una secuencia de escape ANSI CSI. Es la forma más rápida de repetir un personaje.

#!/usr/bin/env bash

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

O estáticamente:

Imprima una línea de 80 veces =:

printf '=\e[80b\n'

Limitaciones:

  • No todas las terminales entienden repeat_char secuencia ANSI CSI.
  • Solo se pueden repetir los caracteres ISO-ASCII o de byte único.
  • Repita las paradas en la última columna, por lo que puede usar un valor grande para llenar una línea completa independientemente del ancho del terminal.
  • La repetición es solo para mostrar. La captura de resultados en una variable de shell no expandirá la repeat_charsecuencia ANSI CSI al carácter repetido.
Léa Gris
fuente
1
Nota menor: REP (CSI b) debería ajustarse normalmente si el terminal está en modo de ajuste.
jerch
3

Esto es lo que uso para imprimir una línea de caracteres en la pantalla en Linux (según el ancho del terminal / pantalla)

Imprima "=" en la pantalla:

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

Explicación:

Imprima un signo igual tantas veces como la secuencia dada:

printf '=%.0s' #sequence

Use la salida de un comando (esta es una característica bash llamada Sustitución de comandos):

$(example_command)

Da una secuencia, he usado del 1 al 20 como ejemplo. En el comando final se usa el comando tput en lugar de 20:

seq 1 20

Indique el número de columnas que se usan actualmente en la terminal:

tput cols
mattbell87
fuente
2
for i in {1..100}
do
  echo -n '='
done
echo
Ignacio Vazquez-Abrams
fuente
2
repeat() {
    # $1=number of patterns to repeat
    # $2=pattern
    printf -v "TEMP" '%*s' "$1"
    echo ${TEMP// /$2}
}
WSimpson
fuente
2

Lo más simple es usar este one-liner en csh / tcsh:

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

Shawn Givler
fuente
2

Una alternativa más elegante a la solución Python propuesta podría ser:

python -c 'print "="*(1000)'
Anas Tiour
fuente
1

En caso de que desee repetir un carácter n veces siendo un número VARIABLE de veces dependiendo de, por ejemplo, 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========  
Raul Baron
fuente
lengthno funcionará expr, probablemente quisiste decir n=$(expr 10 - ${#vari}); Sin embargo, es más simple y más eficiente de utilizar expansión aritmética de Bash: n=$(( 10 - ${#vari} )). Además, en el centro de su respuesta está el enfoque de Perl en el que el OP está buscando una alternativa Bash .
mklement0
1

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

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

Por supuesto, también puedes usar printf para eso, pero no es de mi agrado:

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.

Xennex81
fuente
En golpe y con una n positiva: while (( i-- )); do echo -n " "; donefunciona.
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
}

Ejecuciones de muestra

$ repeatString 'a1' 10 
a1a1a1a1a1a1a1a1a1a1

$ repeatString 'a1' 0 

$ repeatString '' 10 

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

Nam Nguyen
fuente
1

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

Puede hacer esto echosi lo echosigue sed:

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

En realidad, eso echoes innecesario allí.

DaBler
fuente
1

Mi respuesta es un poco más complicada, y probablemente no sea perfecta, pero para aquellos que buscan generar grandes números, pude hacer alrededor de 10 millones en 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}
}
Ogro plateado
fuente
1

Lo más simple es usar este one-liner en bash:

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

shahin aref
fuente
1

Otra opción es usar GNU seq y eliminar todos los números y líneas nuevas que genera:

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

Este comando imprime el #personaje 100 veces.

sigalor
fuente
1

La mayoría de las soluciones existentes en todo dependen de {1..10}apoyo sintaxis de la cáscara, que es bash- y zsh- específica, y no funciona en tcshOpenBSD de kshy la mayoría no fiestash .

Lo siguiente debería funcionar en OS X y todos los sistemas * BSD en cualquier shell; de hecho, puede usarse para generar una matriz completa de varios tipos de espacio decorativo:

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

Lamentablemente, no tenemos una nueva línea final; que se puede arreglar con un extra printf '\n'después del doblez:

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

Referencias

cnst
fuente
0

Mi propuesta (aceptando valores variables para n):

n=100
seq 1 $n | xargs -I {} printf =
Sopalajo de Arrierez
fuente