bash - programas - scripts linux ejercicios resueltos
¿Cómo puedo repetir un personaje en bash? (19)
¿Cómo podría hacer esto con echo
?
perl -E ''say "=" x 100''
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 = | head -10 | paste -s -d '''' - ========== ubuntu@ubuntu:~$ yes = | head -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.
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 objetivo aquí es repetir un solo carácter y el shell es bash, probablemente sea seguro usar
echo
lugar deprintf
. Y leí la descripción del problema en esta pregunta como expresar una preferencia para imprimir conecho
. La definición de función anterior funciona en bash y ksh93 . Aunqueprintf
es más portable (y debería usarse generalmente para este tipo de cosas), la sintaxis deecho
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 paraecho
. zsh hace esto. Y definitivamente existenecho
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 deecho
).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
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.
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 " " "="
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 :)
No hay una manera simple. Evite bucles utilizando printf
y sustitución.
str=$(printf "%40s")
echo ${str// /rep}
# echoes "rep" 40 times.
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.
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.
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.
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
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 envarname
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 devar
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}"
}
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.
- En particular, evite el reemplazo de subcadena global de Bash con cadenas grandes
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 de inicio de los servicios externos 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
yperl
.
- 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 BSDawk
(como también se encuentra en OSX) - es notablemente más lento quegawk
(GNU Awk) y especialmentemawk
. - 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: <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
#!/usr/bin/awk -f
BEGIN {
OFS = "="
NF = 100
print
}
O
#!/usr/bin/awk -f
BEGIN {
while (z++ < 100) printf "="
}
for i in {1..100}
do
echo -n ''=''
done
echo
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
repeat() {
# $1=number of patterns to repeat
# $2=pattern
printf -v "TEMP" ''%*s'' "$1"
echo ${TEMP// /$2}
}