Comment puis-je répéter un personnage en bash?

Comment pourrais-je faire cela avec l’ echo ?

 perl -E 'say "=" x 100' 

Vous pouvez utiliser:

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

Comment ça marche:

Bash se développe {1..100} pour que la commande devienne:

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

J’ai défini le format de printf sur =%.0s ce qui signifie qu’il imprimera toujours un seul = quel que soit l’argument donné. Par conséquent, il imprime 100 = s.

Pas facile Mais par exemple:

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

Ou peut-être un moyen conforme aux normes:

 printf %100s |tr " " "=" 

Il y a aussi un tput rep , mais en ce qui concerne mes terminaux sous la main (xterm et linux), ils ne semblent pas le supporter 🙂

Il y a plus d’une façon de le faire.

En utilisant une boucle:

  • L’extension d’accolade peut être utilisée avec des littéraux entiers:

     for i in {1..100}; do echo -n =; done 
  • Une boucle C-like permet l’utilisation de variables:

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

Utilisation de la commande printf :

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

La spécification d'une précision ici tronque la chaîne pour qu'elle corresponde à la largeur spécifiée ( 0 ). Comme printf réutilise la chaîne de format pour consumr tous les arguments, cela imprime simplement "=" 100 fois.

En utilisant head ( printf , etc) et tr :

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

Astuce du chapeau à @ gniourf_gniourf pour sa consortingbution.

Remarque: cette réponse ne répond pas à la question initiale, mais complète les réponses existantes et utiles en comparant les performances .

Les solutions sont comparées en termes de rapidité d’exécution uniquement – les besoins en mémoire ne sont pas pris en compte (ils varient d’une solution à l’autre et peuvent être importants avec un grand nombre de répétitions).

Résumé:

  • Si votre nombre de répétitions est faible , disons jusqu’à environ 100, cela vaut la peine d’ utiliser les solutions Bash uniquement , car le coût de démarrage des utilitaires externes est important, en particulier celui de Perl.
    • En termes pragmatiques, cependant, si vous n’avez besoin que d’ une seule instance de répétition de caractères, toutes les solutions existantes peuvent convenir.
  • Avec un grand nombre de répétitions , utilisez des utilitaires externes , car ils seront beaucoup plus rapides.
    • En particulier, évitez de remplacer la sous-chaîne globale de Bash par de grandes chaînes
      (par exemple, ${var// /=} ), car il est trop lent.

Les temps suivants ont été pris sur un iMac fin 2012 avec un processeur Intel Core i5 à 3,2 GHz et un disque Fusion, exécutant OSX 10.10.4 et bash 3.2.57, soit la moyenne de 1 000 exécutions.

Les entrées sont les suivantes:

  • listés par ordre croissant de durée d’exécution (le plus rapide en premier)
  • préfixé par:
    • M … une solution potentiellement multi- caractères
    • S … une solution à caractère unique
    • P … une solution compatible POSIX
  • suivi d’une brève description de la solution
  • suffixé avec le nom de l’auteur de la réponse d’origine

  • Petite répétition: 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 
  • Les solutions Bash uniquement mènent le peloton – mais seulement avec un nombre de répétitions aussi petit! (voir ci-dessous).
  • Le coût de démarrage des utilitaires externes est important ici, en particulier pour Perl. Si vous devez appeler cela en boucle – avec de petits nombres de répétitions à chaque itération – évitez les solutions multi-utilitaires, awk et perl .

  • Grand nombre de répétitions: 1000000 (1 million)
 [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 solution Perl de la question est de loin la plus rapide.
  • Le remplacement global de la chaîne de caractères de Bash ( ${foo// /=} ) est inexplicablement lent en ce qui concerne les chaînes volumineuses, et a été retiré du jeu (environ 50 minutes (!) Dans Bash 4.3.30, et encore plus dans Bash) 3.2.57 – Je n’ai jamais attendu la fin.
  • Les boucles Bash sont lentes et les boucles arithmétiques ( (( i= 0; ... )) ) sont plus lentes que celles développées par accolade ( {1..n} ) – bien que les boucles arithmétiques soient plus efficaces en termes de mémoire.
  • awk fait référence à awk BSD (comme on le trouve également sur OSX) – il est nettement plus lent que gawk (GNU Awk) et surtout mawk .
  • Notez qu’avec de grands comptes et plusieurs caractères. chaînes, la consommation de mémoire peut devenir une considération – les approches diffèrent à cet égard.

Voici le script Bash ( testrepeat ) qui a produit ce qui précède. Il faut 2 arguments:

  • le nombre de répétitions de caractères
  • éventuellement, le nombre de tests à effectuer et à calculer le temps moyen à partir de

En d’autres termes: les délais ci-dessus ont été obtenus avec le testrepeat 100 1000 et le 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 

Je viens de trouver un moyen très facile de faire cela en utilisant seq:

MISE À JOUR: Cela fonctionne sur le seq BSD fourni avec OS X. YMMV avec d’autres versions

 seq -f "#" -s '' 10 

Imprime ‘#’ 10 fois, comme ceci:

 ########## 
  • -f "#" définit la chaîne de format pour ignorer les nombres et simplement imprimer # pour chacun.
  • -s '' définit le séparateur sur une chaîne vide pour supprimer les nouvelles lignes insérées entre chaque numéro
  • Les espaces après -f et -s semblent être importants.

EDIT: Ici, c’est dans une fonction pratique …

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

Ce que vous pouvez appeler comme ça …

 repeat "#" 10 

NOTE: Si vous répétez # alors les citations sont importantes!

Voici deux manières intéressantes:

 Ubuntu @ Ubuntu: ~ $ oui = |  tête -10 |  paste -s -d '' -
 ==========
 Ubuntu @ Ubuntu: ~ $ oui = |  tête -10 |  tr -d "\ n"
 ========== Ubuntu @ ubuntu: ~ $ 

Notez que ces deux sont subtilement différents – La méthode de paste se termine par une nouvelle ligne. La méthode tr ne le fait pas.

Il n’y a pas de moyen simple. Évitez les boucles en utilisant printf et la substitution.

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

Ou

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

Exemple

Je suppose que le but initial de la question était de le faire avec les commandes intégrées du shell. Donc, for boucles et les printf serait légitime, alors que rep , perl , et aussi jot ci-dessous ne le seraient pas. Toujours la commande suivante

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

par exemple, imprime une ligne de fenêtre de \/\/\/\/\/\/\/\/\/\/\/\/

Si vous souhaitez une conformité et une cohérence POSIX entre les différentes implémentations d’ echo et de printf , et / ou de shells autres que 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) 

… produira la même sortie que perl -E 'say "=" x 100' peu près partout.

Comme d’autres l’ont déjà dit, l’ élargissement des accolades de bash précède l’ extension des parameters , donc { m , n } plages ne peuvent contenir que des littéraux. seq et jot fournissent des solutions propres mais ne sont pas entièrement portables d’un système à un autre, même si vous utilisez le même shell sur chacun. (Bien que seq soit de plus en plus disponible, par exemple dans FreeBSD 9.3 et supérieur ), eval et d’autres formes d’indirection fonctionnent toujours mais sont quelque peu inélégantes.

Heureusement, bash prend en charge le style C pour les boucles (avec des expressions arithmétiques uniquement). Alors, voici une manière concise “pure bash”:

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

Cela prend le nombre de répétitions comme premier argument et la chaîne à répéter (qui peut être un seul caractère, comme dans la description du problème) comme deuxième argument. repecho 7 b sorties bbbbbbb (terminé par une nouvelle ligne).

Dennis Williamson a essentiellement proposé cette solution il y a quatre ans dans son excellente réponse à la création de chaîne de caractères répétés dans un script shell . Mon corps de fonction diffère légèrement du code:

  • Comme l'accent est mis ici sur la répétition d'un seul caractère et que le shell est bash, il est sans doute préférable d'utiliser echo au lieu de printf . Et je lis la description du problème dans cette question comme exprimant une préférence pour imprimer avec echo . La définition de la fonction ci-dessus fonctionne dans bash et ksh93 . Bien que printf soit plus portable (et devrait normalement être utilisé pour ce genre de chose), la syntaxe de l' echo est sans doute plus lisible.

    Certaines fonctions intégrées à certains shells interprètent - par elles-mêmes comme une option - même si la signification habituelle de - , pour utiliser stdin pour l’entrée, est absurde pour l’ echo . zsh le fait. Et il existe certainement des echo qui ne reconnaissent pas -n , car ce n'est pas standard . (Beaucoup de shells de style Bourne n'acceptent pas le style C pour les boucles, leur comportement d' echo n'a donc pas à être pris en compte.)

  • Ici, la tâche consiste à imprimer la séquence; là , c'était pour l'assigner à une variable.

Si $n est le nombre de répétitions souhaité et que vous n'avez pas à le réutiliser, et que vous voulez quelque chose de plus court encore:

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

n doit être une variable - cette méthode ne fonctionne pas avec les parameters de position. $s est le texte à répéter.

En bash 3.0 ou supérieur

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

Une méthode purement Bash sans eval , sans sous-couches, sans outils externes, sans expansions (vous pouvez avoir le nombre à répéter dans une variable):

Si on vous donne une variable n qui se développe en un nombre (non négatif) et une variable, par exemple,

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

Vous pouvez faire une fonction avec ceci:

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

Avec cet ensemble:

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

Pour ce petit truc, nous utilisons beaucoup printf avec:

  • -v varname : au lieu d’imprimer en sortie standard, printf mettra le contenu de la chaîne formatée dans la variable varname .
  • ‘% * s’: printf utilisera l’argument pour imprimer le nombre d’espaces correspondant. Par exemple, printf '%*s' 42 imprimera 42 espaces.
  • Enfin, lorsque nous avons le nombre d’espaces voulu dans notre variable, nous utilisons une extension de paramètre pour remplacer tous les espaces par notre modèle: ${var// /$pattern} développera l’extension de var avec tous les espaces remplacés par l’expansion de $pattern .

Vous pouvez également vous débarrasser de la variable tmp dans la fonction de repeat en utilisant l’expansion indirecte:

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

Dans le cas où vous voulez répéter un caractère n fois n VARIABLE nombre de fois en fonction, par exemple, de la longueur d’une chaîne, vous pouvez le faire:

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

Il affiche:

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

C’est la version la plus longue de ce qu’Eliah Kagan a épousé:

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

Bien sûr, vous pouvez également utiliser printf, mais pas vraiment à mon goût:

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

Cette version est compatible Dash:

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

avec i étant le numéro initial.

Python est omniprésent et fonctionne de la même façon partout.

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

Le caractère et le nombre sont transmis en tant que parameters séparés.

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

Échantillons courus

 $ repeatSsortingng 'a1' 10 a1a1a1a1a1a1a1a1a1a1 $ repeatSsortingng 'a1' 0 $ repeatSsortingng '' 10 

Bibliothèque de référence à l’ adresse : https://github.com/gdbtek/linux-cookbooks/blob/master/libraries/util.bash