Comment puis-je répéter un personnage dans Bash?


240

Comment pourrais-je faire ça avec echo?

perl -E 'say "=" x 100'

Malheureusement, ce n'est pas Bash.
solidsnack

1
pas avec écho, mais sur le même sujet ruby -e 'puts "=" * 100'oupython -c 'print "=" * 100'
Evgeny

1
Grande question. Très bonnes réponses. J'ai utilisé une des réponses dans un vrai travail ici, que je posterai comme exemple: github.com/drbeco/oldfiles/blob/master/oldfiles (utilisé printfavec seq)svrb=`printf '%.sv' $(seq $vrb)`
Dr Beco

Une solution générique pour imprimer quoi que ce soit (1 ou plusieurs caractères, même les sauts de ligne): Repeat_this () {i = 1; while ["$ i" -le "$ 2"]; faire printf "% s" "$ 1"; i = $ (($ i + 1)); terminé ; printf '\ n';}. Utilisez comme ceci: Repeat_this "something" Number_of_repetitions. Par exemple, pour présenter la répétition de 5 fois quelque chose, y compris 3 sauts de ligne: Repeat_this "$ (printf '\ n \ n \ nthis')" 5. Le dernier printf '\ n' peut être retiré (mais je l'ai mis pour créer des fichiers texte, et ceux-ci ont besoin d'une nouvelle ligne comme dernier caractère!)
Olivier Dulac

Réponses:


396

Vous pouvez utiliser:

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

Comment cela fonctionne:

Bash développe {1..100} de sorte que la commande devient:

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

J'ai défini le format de printf, =%.0sce qui signifie qu'il imprimera toujours un seul, =quel que soit l'argument qui lui est donné. Il imprime donc 100 =s.


14
Excellente solution qui fonctionne raisonnablement bien même avec de grands nombres de répétitions. Voici un wrapper de fonction avec lequel vous pouvez invoquer repl = 100, par exemple (une evalastuce est malheureusement nécessaire pour baser l'expansion d'accolade sur une variable):repl() { printf "$1"'%.s' $(eval "echo {1.."$(($2))"}"); }
mklement0

7
Est-il possible de fixer la limite supérieure à l'aide d'un var? J'ai essayé et je n'arrive pas à le faire fonctionner.
Mike Purcell

70
Vous ne pouvez pas utiliser de variables dans l'extension d'accolade. Utilisez seqplutôt par exemple $(seq 1 $limit).
dogbane

11
Si vous le fonctionnalisez, il est préférable de le réorganiser de $s%.0sà %.0s$ssinon les tirets provoquent une printferreur.
KomodoDave

5
Cela m'a fait remarquer un comportement de Bash printf: il continue d'appliquer la chaîne de format jusqu'à ce qu'il n'y ait plus d'arguments. J'avais supposé qu'il n'avait traité la chaîne de format qu'une seule fois!
Jeenu

89

Pas facile. Mais par exemple:

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

Ou peut-être d'une manière conforme aux normes:

printf %100s |tr " " "="

Il y en a aussi un tput rep, mais quant à mes terminaux à portée de main (xterm et linux) ils ne semblent pas le supporter :)


3
Notez que la première option avec seq imprime une de moins que le nombre donné, de sorte que l'exemple imprime 99 =caractères.
Camilo Martin

13
printf trest la seule solution POSIX car seq, yeset {1..3}ne sont pas POSIX.
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

2
Pour répéter une chaîne plutôt qu'un simple caractère: printf %100s | sed 's/ /abc/g'- affiche 'abcabcabc ...'
John Rix

3
+1 pour n'utiliser aucune boucle et une seule commande externe ( tr). Vous pouvez également l'étendre à quelque chose comme printf "%${COLUMNS}s\n" | tr " " "=".
musiphil

2
@ mklement0 Eh bien, j'espérais que vous comptiez la dernière nouvelle ligne par erreur avec wc. La seule conclusion que je peux en tirer est " seqne devrait pas être utilisée".
Camilo Martin

51

Pointe du chapeau à @ gniourf_gniourf pour sa contribution.

Remarque: Cette réponse ne répond pas à la question d'origine, mais complète les réponses utiles existantes en comparant les performances .

Les solutions sont comparées uniquement en termes de vitesse d'exécution - les besoins en mémoire ne sont pas pris en compte (ils varient selon les solutions et peuvent être importants avec de grands nombres de répétitions).

Résumé:

  • Si votre nombre de répétitions est faible , disons jusqu'à environ 100, cela vaut la peine d'aller avec les solutions Bash uniquement , car le coût de démarrage des utilitaires externes est important, en particulier celui de Perl.
    • 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 de grands nombres de répétitions , utilisez des utilitaires externes , car ils seront beaucoup plus rapides.
    • En particulier, évitez le remplacement global de la sous-chaîne de Bash par de grandes chaînes
      (par exemple, ${var// /=}), car il est extrêmement lent.

Les temporisations suivantes sont prises sur un iMac fin 2012 avec un processeur Intel Core i5 à 3,2 GHz et un Fusion Drive, exécutant OSX 10.10.4 et bash 3.2.57, et représentent la moyenne de 1000 exécutions.

Les entrées sont:

  • répertorié 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 seule solution -character seule
    • P ... une solution compatible POSIX
  • suivi d'une brève description de la solution
  • suffixé du nom de l'auteur de la réponse d'origine

  • Petit nombre de répétitions: 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 importe ici, en particulier celui de Perl. Si vous devez appeler cela en boucle - avec de petits nombres de répétitions à chaque itération - évitez le multi-utilitaire awket les perlsolutions.

  • 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 des chaînes de Bash ( ${foo// /=}) est inexplicablement lent avec des chaînes de grande taille, et a été retiré du jeu (a pris environ 50 minutes (!) Dans Bash 4.3.30, et encore plus longtemps dans Bash 3.2.57 - je n'ai jamais attendu pour finir).
  • Les boucles de bash sont lentes et les boucles arithmétiques ( (( i= 0; ... ))) sont plus lentes que celles à croisillons ( {1..n}) - bien que les boucles arithmétiques soient plus efficaces en mémoire.
  • awkfait référence à BSD awk (comme on le trouve également sur OSX) - il est sensiblement plus lent que gawk(GNU Awk) et surtout mawk.
  • Notez qu'avec de grands nombres et plusieurs caractères. cordes, 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
  • facultativement, le nombre de tests à effectuer et de calculer le temps moyen à partir de

En d'autres termes: les timings ci-dessus ont été obtenus avec testrepeat 100 1000ettestrepeat 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

Il est intéressant de voir la comparaison de synchronisation, mais je pense que dans de nombreux programmes, la sortie est tamponnée, de sorte que leur synchronisation peut être modifiée si la mise en mémoire tampon a été désactivée.
Sergiy Kolodyazhnyy

In order to use brace expansion with a variable, we must use `eval`👍
pyb

2
Ainsi, la solution perl (sid_com) est fondamentalement la plus rapide ... une fois que la surcharge initiale de lancement de perl est atteinte. (il passe de 59 ms pour une petite répétition à 67 ms pour un million de répétitions ... donc le perl forking a pris environ 59 ms sur votre système)
Olivier Dulac

46

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

Utiliser une boucle:

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

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

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

Utilisation de la fonction printfintégrée:

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

La spécification d'une précision ici tronque la chaîne pour l'adapter à la largeur spécifiée ( 0). Comme printfréutilise la chaîne de format pour consommer tous les arguments, cela imprime simplement "="100 fois.

Utilisation de head( printf, etc) et tr:

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

2
++ pour la solution head/ tr, qui fonctionne bien même avec un nombre de répétitions élevé (petite mise en garde: head -cn'est pas compatible POSIX, mais BSD et GNU l' headimplémentent); alors que les deux autres solutions seront lentes dans ce cas, elles ont également l'avantage de fonctionner avec des chaînes multi- caractères.
mklement0

1
L' utilisation yeset head- utile si vous voulez un certain nombre de nouvelles lignes: yes "" | head -n 100. trpeut faire imprimer n'importe quel caractère:yes "" | head -n 100 | tr "\n" "="; echo
loxaxs

Un peu surprenant: dd if=/dev/zero count=1 bs=100000000 | tr '\0' '=' >/dev/nullest beaucoup plus lent que la head -c100000000 < /dev/zero | tr '\0' '=' >/dev/nullversion. Bien sûr, vous devez utiliser une taille de bloc de 100M + pour mesurer la différence de temps de manière raisonnable. 100M octets prend 1,7 s et 1 s avec les deux versions respectives illustrées. J'ai enlevé le tr et je l'ai simplement vidé /dev/nullet j'ai obtenu 0,287 s pour la headversion et 0,675 s pour la ddversion pour un milliard d'octets.
Michael Goldshteyn

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

31

Je viens de trouver un moyen très simple de le faire en utilisant seq:

MISE À JOUR: Cela fonctionne sur le BSD seqfourni 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 sauts de ligne qui seq insère entre chaque numéro
  • Les espaces après -fet -ssemblent importants.

EDIT: Ici, c'est dans une fonction pratique ...

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

Que vous pouvez appeler comme ça ...

repeat "#" 10

REMARQUE: si vous répétez, #les citations sont importantes!


7
Cela me donne seq: format ‘#’ has no % directive. seqest pour les nombres, pas pour les chaînes. Voir gnu.org/software/coreutils/manual/html_node/seq-invocation.html
John B

Ah, donc j'utilisais la version BSD de seq trouvée sur OS X. Je mettrai à jour la réponse. Quelle version utilisez-vous?
Sam Salisbury

J'utilise seq de GNU coreutils.
John B

1
@JohnB: BSD seqest intelligemment réutilisé ici pour répliquer les chaînes : la chaîne de format transmise à -f- normalement utilisée pour formater les nombres générés - contient uniquement la chaîne à répliquer ici afin que la sortie contienne uniquement des copies de cette chaîne. Malheureusement, GNU seqinsiste sur la présence d'un format numérique dans la chaîne de format, ce qui est l'erreur que vous voyez.
mklement0

1
Bien fait; fonctionne également avec des chaînes multi- caractères. Veuillez utiliser "$1"(guillemets doubles), afin que vous puissiez également passer des caractères tels que '*'et des chaînes avec des espaces blancs intégrés. Enfin, si vous voulez pouvoir l'utiliser %, vous devez le doubler (sinon vous seqpenserez que cela fait partie d'une spécification de format telle que %f); utiliser s'en "${1//%/%%}"occuperait. Puisque (comme vous le mentionnez) vous utilisez BSD seq , cela fonctionnera sur les systèmes d'exploitation de type BSD en général (par exemple, FreeBSD) - en revanche, cela ne fonctionnera pas sur Linux , où GNU seq est utilisé.
mklement0

18

Voici deux façons intéressantes:

ubuntu @ ubuntu: ~ $ yes = | tête -10 | coller -s -d '' -
==========
ubuntu @ ubuntu: ~ $ yes = | tête -10 | tr -d "\ n"
========== ubuntu @ ubuntu: ~ $ 

Notez que ces deux sont subtilement différents - La pasteméthode se termine par une nouvelle ligne. La trméthode ne fonctionne pas.


1
Bien fait; veuillez noter que BSD paste nécessite inexplicablement -d '\0'de spécifier un délimiteur vide, et échoue avec -d ''- -d '\0'devrait fonctionner avec toutes les pasteimplémentations compatibles POSIX et fonctionne également avec GNU paste .
mklement0

Esprit similaire, avec moins d'outils hors-bord:yes | mapfile -n 100 -C 'printf = \#' -c 1
évêque

@bishop: Bien que votre commande crée en effet un sous-shell de moins, elle est toujours plus lente pour les nombres de répétitions plus élevés, et pour les nombres de répétitions inférieurs, la différence n'a probablement pas d'importance; le seuil exact dépend probablement à la fois du matériel et du système d'exploitation, par exemple, sur ma machine OSX 10.11.5, cette réponse est déjà plus rapide à 500; essayez time yes = | head -500 | paste -s -d '\0' -; time yes | mapfile -n 500 -C 'printf = \#' -c 1. Plus important encore, cependant: si vous utilisez de printftoute façon, vous pouvez tout aussi bien adopter l'approche à la fois plus simple et plus efficace de la réponse acceptée:printf '%.s=' $(seq 500)
mklement0

13

Il n'y a pas de moyen simple. Évitez l'utilisation printfet la substitution des boucles .

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

2
Nice, mais ne fonctionne raisonnablement qu'avec de petits nombres de répétitions. Voici un wrapper de fonction qui peut être invoqué comme repl = 100, par exemple (ne produit pas de fin \n):repl() { local ts=$(printf "%${2}s"); printf %s "${ts// /$1}"; }
mklement0

1
@ mklement0 Ravi de vous de fournir des versions fonctionnelles des deux solutions, +1 sur les deux!
Camilo Martin

8

Si vous souhaitez la conformité POSIX et la cohérence entre les différentes implémentations de echoet printf, et / ou des shells autres que simplement 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'presque partout.


1
Le problème est que ce seqn'est pas un utilitaire POSIX (bien que les systèmes BSD et Linux en aient des implémentations) - vous pouvez faire de l'arithmétique shell POSIX avec une whileboucle à la place, comme dans la réponse de @ Xennex81 (avec printf "=", comme vous le suggérez correctement, plutôt que echo -n).
mklement0

1
Oups, vous avez tout à fait raison. Des choses comme ça passent parfois devant moi, car cette norme n'a pas de sens. calest POSIX. seqn'est pas. Quoi qu'il en soit, plutôt que de réécrire la réponse avec une boucle while (comme vous le dites, c'est déjà dans d'autres réponses), j'ajouterai une fonction RYO. Plus pédagogique de cette façon ;-).
Geoff Nixon

8

La question était de savoir comment le faire avec echo:

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

Cela fera exactement la même chose perl -E 'say "=" x 100'mais avec echoseulement.


Maintenant, c'est inhabituel, si vous n'y intégrez pas d'espace arrière supplémentaire ou nettoyez-le en utilisant: echo -e $ _ {1..100} '\ b =' | col
anthony

1
Mauvaise idée. Cela échouera si $_1, $_2ou toute autre des cent variables a des valeurs.
John Kugelman,

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

C'est dégoûtant . J'adore: D
dimo414

6

Une manière pure de Bash sans eval, aucun sous-shell, aucun outil externe, aucune extension d'accolade (c.-à-d., Vous pouvez avoir le nombre à répéter dans une variable):

Si vous recevez une variable nqui se développe en un nombre (non négatif) et une variable pattern, par exemple,

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

Vous pouvez créer 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 cette petite astuce, nous utilisons printfbeaucoup avec:

  • -v varname: au lieu d'imprimer sur la sortie standard, printfmettra le contenu de la chaîne formatée en variable varname.
  • '% * s': printfutilisera l'argument pour imprimer le nombre d'espaces correspondant. Par exemple, printf '%*s' 42imprimera 42 espaces.
  • Enfin, lorsque nous avons le nombre souhaité d'espaces dans notre variable, nous utilisons un paramètre expansion pour remplacer tous les espaces par notre modèle: ${var// /$pattern}va s'étendre à l'expansion de varavec tous les espaces remplacés par l'expansion de $pattern.

Vous pouvez également vous débarrasser de la tmpvariable dans la repeatfonction 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}"
}

Variation intéressante pour transmettre le nom de la variable. Bien que cette solution soit appropriée pour les comptes de répétition jusqu'à environ 1000 (et donc probablement très bien pour la plupart des applications réelles, si je devine), elle devient très lente pour les comptes plus élevés (voir ci-dessous). commentaire).
mklement0

Il semble que bashles opérations globales de remplacement de chaîne dans le contexte de l'expansion des paramètres ( ${var//old/new}) soient particulièrement lentes: atrocement lent en bash 3.2.57, et lent en bash 4.3.30, au moins sur mon système OSX 10.10.3 sur une machine Intel Core i5 à 3,2 Ghz: avec un compte de 1000, les choses sont lentes ( 3.2.57) / rapides ( 4.3.30): 0,1 / 0,004 secondes. Augmenter le nombre à 10 000 donne des nombres étonnamment différents: repeat 10000 = varprend environ 80 secondes (!) En bash 3.2.57et environ 0,3 seconde en bash 4.3.30(beaucoup plus rapide qu'activé 3.2.57, mais toujours lent).
mklement0

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

Ou

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

Exemple


3
Bien fait; il est conforme à POSIX et raisonnablement rapide même avec un nombre de répétitions élevé, tout en prenant également en charge les chaînes multi-caractères. Voici la version du shell: awk 'BEGIN { while (c++ < 100) printf "=" }'. Enveloppé dans une fonction d'enveloppe paramétrée (invoquer comme repeat 100 =, par exemple): repeat() { awk -v count="$1" -v txt=".$2" 'BEGIN { txt=substr(txt, 2); while (i++ < count) printf txt }'; }. (Le .préfixe factice et l' substrappel complémentaire sont nécessaires pour contourner un bogue dans BSD awk, où le passage d'une valeur de variable qui commence par =rompt la commande.)
mklement0

1
La NF = 100solution est très intelligente (mais pour obtenir 100 =, vous devez utiliser NF = 101). Les mises en garde sont qu'il se bloque BSD awk(mais il est très rapide avec gawket encore plus rapide avec mawk), et qui traite ni POSIX assigner à NF, ni l' utilisation de champs dans les BEGINblocs. Vous pouvez également le faire fonctionner dans BSD awkavec un léger ajustement: awk 'BEGIN { OFS = "="; $101=""; print }'(mais curieusement, dans BSD awkce n'est pas plus rapide que la solution de boucle). En tant que solution shell paramétrées: repeat() { awk -v count="$1" -v txt=".$2" 'BEGIN { OFS=substr(txt, 2); $(count+1)=""; print }'; }.
mklement0

Note aux utilisateurs - L'astuce NF = 100 provoque une erreur de segment sur les anciens awk. C'est original-awkle nom sous Linux de l'ancien awk similaire à awk de BSD, qui a également été signalé en panne, si vous voulez essayer cela. Notez que le plantage est généralement la première étape vers la recherche d'un bogue exploitable. Cette réponse est donc la promotion du code non sécurisé.

2
Note aux utilisateurs - original-awkn'est pas standard et n'est pas recommandée
Steven Penny

Une alternative au premier extrait de code peut être awk NF=100 OFS='=' <<< ""(en utilisant bashet gawk)
oliv

4

Je suppose que le but initial de la question était de le faire uniquement avec les commandes intégrées du shell. Ainsi , les forboucles et printfs seraient légitimes, alors que rep, perlet aussi jotci - dessous ne serait pas. Pourtant, la commande suivante

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

par exemple, imprime une ligne de \/\/\/\/\/\/\/\/\/\/\/\/


2
Bien fait; cela fonctionne bien même avec un nombre de répétitions élevé (tout en prenant également en charge les chaînes à plusieurs caractères). Pour mieux illustrer l'approche, voici l'équivalent de la commande de l'OP: jot -s '' -b '=' 100. La mise en garde est que, bien que les plates-formes de type BSD, y compris OSX, soient fourniesjot , les distributions Linux ne le sont pas .
mklement0

1
Merci, j'aime encore mieux votre utilisation de -s. J'ai changé mes scripts.
Stefan Ludwig

Sur les systèmes récents basés sur Debian , apt install athena-jotfournirait jot.
agc

4

Comme d'autres l'ont dit, l' expansion des accolades en bash précède l' expansion des paramètres , les plages ne peuvent donc contenir que des littéraux. et fournir des solutions propres mais ne sont pas entièrement portables d'un système à l'autre, même si vous utilisez le même shell sur chacun. (Bien qu'il soit de plus en plus disponible; par exemple, dans FreeBSD 9.3 et supérieur .) Et d'autres formes d'indirection fonctionnent toujours mais sont quelque peu inélégantes.{m,n}seqjotseqeval

Heureusement, bash prend en charge le style C pour les boucles (avec des expressions arithmétiques uniquement). Voici donc une méthode concise de "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 bsorties bbbbbbb(terminées par une nouvelle ligne).

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

  • Puisque l'accent est mis ici sur la répétition d'un seul caractère et que le shell est bash, il est probablement sûr d'utiliser à la echoplace de printf. Et j'ai lu la description du problème dans cette question comme exprimant une préférence pour imprimer avec echo. La définition de fonction ci-dessus fonctionne en bash et ksh93 . Bien qu'elle printfsoit plus portable (et devrait généralement être utilisée pour ce genre de choses), echola syntaxe de est sans doute plus lisible.

    Certains echocomposants internes de shells s'interprètent -par eux-mêmes comme une option - même si la signification habituelle de -, pour utiliser stdin comme entrée, n'a pas de sens pour echo. zsh fait cela. Et il existe certainement des echos qui ne reconnaissent pas -n, car ce n'est pas standard . (De nombreux shells de style Bourne n'acceptent pas du tout le style C pour les boucles, donc leur echocomportement n'a pas besoin d'être pris en compte ..)

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

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

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

ndoit être une variable - cette méthode ne fonctionne pas avec les paramètres de position. $sest le texte à répéter.


2
Évitez fortement de faire des versions en boucle. printf "%100s" | tr ' ' '='est optimal.
ocodo

De bonnes informations de fond et des félicitations pour empaqueter la fonctionnalité en tant que fonction, qui fonctionne zshégalement également. L'approche de l'écho en boucle fonctionne bien pour les petits nombres de répétitions, mais pour les plus grands, il existe des alternatives compatibles POSIX basées sur les utilitaires , comme en témoigne le commentaire de @ Slomojo.
mklement0

L'ajout de parenthèses autour de votre boucle plus courte préserve la valeur de n sans affecter les échos:(while ((n--)); do echo -n "$s"; done; echo)

utilisez printf au lieu d'echo! c'est beaucoup plus portable (echo -n ne peut fonctionner que sur certains systèmes). voir unix.stackexchange.com/questions/65803/… (l'une des impressionnantes réponses de Stéphane Chazelas)
Olivier Dulac

@OlivierDulac La question ici concerne bash. Quel que soit le système d'exploitation que vous utilisez, si vous utilisez bash dessus , bash possède une fonction echointégrée qui prend en charge -n. L'esprit de ce que vous dites est absolument correct. printfdevrait presque toujours être préféré à echo, au moins dans une utilisation non interactive. Mais je ne pense pas qu'il soit en aucune façon inapproprié ou trompeur de donner une echoréponse à une question qui en a demandé une et qui a donné suffisamment d'informations pour savoir que cela fonctionnerait . Veuillez également noter que la prise en charge de ((n--))(sans a $) n'est pas garantie par POSIX.
Eliah Kagan

4

Python est omniprésent et fonctionne de la même manière partout.

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

Le caractère et le nombre sont passés en tant que paramètres séparés.


Je pense que c'était l'intention icipython -c "import sys; print(sys.argv[1] * int(sys.argv[2]))" "=" 100
gazhay

@loevborg n'est pas si farfelu?
Sapphire_Brick

3

Dans bash 3.0 ou supérieur

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

3

Un autre moyen de répéter une chaîne arbitraire n fois:

Avantages:

  • Fonctionne avec le shell POSIX.
  • La sortie peut être affectée à une variable.
  • Répète n'importe quelle chaîne.
  • Très rapide même avec de très grandes répétitions.

Les inconvénients:

  • Nécessite la yescommande de Gnu Core Utils .
#!/usr/bin/sh
to_repeat='='
repeat_count=80
yes "$to_repeat" | tr -d '\n' | head -c "$repeat_count"

Avec un terminal ANSI et des caractères US-ASCII à répéter. Vous pouvez utiliser une séquence d'échappement ANSI CSI. C'est le moyen le plus rapide de répéter un caractère.

#!/usr/bin/env bash

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

Ou statiquement:

Imprimez une ligne de 80 fois =:

printf '=\e[80b\n'

Limites:

  • Tous les terminaux ne comprennent pas la repeat_charséquence ANSI CSI.
  • Seuls les caractères US-ASCII ou ISO à un octet peuvent être répétés.
  • La répétition s'arrête à la dernière colonne, vous pouvez donc utiliser une grande valeur pour remplir une ligne entière quelle que soit la largeur du terminal.
  • La répétition est uniquement pour l'affichage. La capture de la sortie dans une variable shell ne développera pas la repeat_charséquence CSI ANSI en caractère répété.

1
Remarque mineure - REP (CSI b) doit se terminer normalement si le terminal est en mode de bouclage.
jerch

3

Voici ce que j'utilise pour imprimer une ligne de caractères sur l'écran sous Linux (en fonction de la largeur du terminal / de l'écran)

Imprimez "=" sur l'écran:

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

Explication:

Imprimez un signe égal autant de fois que la séquence donnée:

printf '=%.0s' #sequence

Utilisez la sortie d'une commande (il s'agit d'une fonction bash appelée Substitution de commande):

$(example_command)

Donnez une séquence, j'ai utilisé 1 à 20 comme exemple. Dans la commande finale, la commande tput est utilisée au lieu de 20:

seq 1 20

Indiquez le nombre de colonnes actuellement utilisées dans le terminal:

tput cols


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

2

Le plus simple est d'utiliser ce one-liner dans csh / tcsh:

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


2

Une alternative plus élégante à la solution Python proposée pourrait être:

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

1

Dans le cas où vous voulez répéter un caractère n fois étant na nombre VARIABLE de fois en fonction, par exemple, de la longueur d'une chaîne, vous pouvez 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========  

lengthne fonctionnera pas expr, vous vouliez probablement dire n=$(expr 10 - ${#vari}); cependant, il est plus simple et plus efficace d'utiliser l' évaluation arithmétique de Bash: n=$(( 10 - ${#vari} )). De plus, au cœur de votre réponse se trouve l'approche très Perl à laquelle l'OP cherche une alternative à Bash .
mklement0

1

Voici la version plus longue de ce qu'Eliah Kagan épousait:

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

Bien sûr, vous pouvez également utiliser printf pour cela, 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 nombre initial.


En bash et avec un n positif: while (( i-- )); do echo -n " "; donefonctionne.

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
}

Exemples de parcours

$ repeatString 'a1' 10 
a1a1a1a1a1a1a1a1a1a1

$ repeatString 'a1' 0 

$ repeatString '' 10 

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


1

Comment pourrais-je faire cela avec l'écho?

Vous pouvez le faire avec echosi echoest suivi de sed:

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

En fait, cela echon'est pas nécessaire là-bas.


1

Ma réponse est un peu plus compliquée, et probablement pas parfaite, mais pour ceux qui cherchent à produire de grands nombres, j'ai pu faire environ 10 millions en 3 secondes.

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

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

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

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

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

1

Le plus simple est d'utiliser ce one-liner en bash:

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


1

Une autre option consiste à utiliser GNU seq et à supprimer tous les numéros et les nouvelles lignes qu'il génère:

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

Cette commande imprime le #caractère 100 fois.


1

La plupart des solutions existantes dépendent toutes de la prise en {1..10}charge de la syntaxe du shell, qui est bash- et zsh- spécifique, et ne fonctionne pas dans tcshou OpenBSD kshet la plupart non bashsh .

Les éléments suivants devraient fonctionner sur OS X et tous les systèmes * BSD dans n'importe quel shell; en fait, il peut être utilisé pour générer toute une matrice de différents types d'espace décoratif:

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

Malheureusement, nous n'obtenons pas de nouvelle ligne de fin; qui peut être fixé par un supplément printf '\n'après le pli:

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

Références:


En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.