Wie kann ich einen Charakter in Bash wiederholen?


240

Wie könnte ich das machen echo?

perl -E 'say "=" x 100'

Leider ist das nicht Bash.
Solidsnack

1
nicht mit Echo, aber zum gleichen Thema ruby -e 'puts "=" * 100'oderpython -c 'print "=" * 100'
Evgeny

1
Gute Frage. Sehr gute Antworten. Ich habe eine der Antworten in einem echten Job hier verwendet, die ich als Beispiel posten werde: github.com/drbeco/oldfiles/blob/master/oldfiles (verwendet printfmit seq)svrb=`printf '%.sv' $(seq $vrb)`
Dr. Beco

Eine generische Lösung zum Drucken von beliebigen Elementen (1 oder mehr Zeichen, einschließlich Zeilenumbrüchen): Repeat_this () {i = 1; während ["$ i" -le "$ 2"]; printf "% s" "$ 1"; i = $ (($ i + 1)); getan ; printf '\ n';}. Verwenden Sie wie folgt: Wiederholen Sie dieses "Etwas" Anzahl der Wiederholungen. Zum Beispiel, um 5-maliges Wiederholen zu zeigen, einschließlich 3 Zeilenumbrüchen: Repeat_this "$ (printf '\ n \ n \ nthis')" 5. Der endgültige Ausdruck '\ n' kann herausgenommen werden (aber ich habe ihn eingefügt, um Textdateien zu erstellen, und diese benötigen eine Zeilenumbruch als letztes Zeichen!)
Olivier Dulac

Antworten:


396

Sie können verwenden:

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

So funktioniert das:

Bash erweitert {1..100}, sodass der Befehl wie folgt lautet:

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

Ich habe das Format von printf festgelegt, =%.0swas bedeutet, dass immer ein einzelnes gedruckt wird, =unabhängig davon, welches Argument angegeben wird. Daher werden 100 =s gedruckt .


14
Großartige Lösung, die auch bei großen Wiederholungszahlen eine recht gute Leistung erbringt. Hier ist ein Funktions-Wrapper, mit dem Sie repl = 100zum Beispiel aufrufen können ( evalleider sind Tricks erforderlich, um die repl() { printf "$1"'%.s' $(eval "echo {1.."$(($2))"}"); }
Klammererweiterung

7
Ist es möglich, die Obergrenze mit einem var festzulegen? Ich habe es versucht und kann es nicht zum Laufen bringen.
Mike Purcell

70
Sie können keine Variablen innerhalb der Klammererweiterung verwenden. Verwenden Sie seqstattdessen z $(seq 1 $limit).
Dogbane

11
Wenn Sie dies funktionalisieren, ist es am besten, es neu anzuordnen $s%.0s, da %.0s$ssonst Striche einen printfFehler verursachen.
KomodoDave

5
Dies hat mich auf ein Verhalten von Bash aufmerksam gemacht printf: Es wird weiterhin die Formatzeichenfolge angewendet, bis keine Argumente mehr vorhanden sind. Ich hatte angenommen, dass es die Formatzeichenfolge nur einmal verarbeitet hat!
Jeenu

89

Kein einfacher Weg. Aber zum Beispiel:

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

Oder vielleicht eine standardkonforme Art:

printf %100s |tr " " "="

Es gibt auch eine tput rep, aber meine Terminals (xterm und Linux) scheinen sie nicht zu unterstützen :)


3
Beachten Sie, dass die erste Option mit seq eins weniger als die angegebene Zahl druckt, sodass in diesem Beispiel 99 =Zeichen gedruckt werden .
Camilo Martin

13
printf trist die POSIX - Lösung nur , weil seq, yesund {1..3}ist nicht POSIX.
Ciro Santilli 法轮功 冠状 病 六四 事件 10

2
So wiederholen Sie eine Zeichenfolge und nicht nur ein einzelnes Zeichen: printf %100s | sed 's/ /abc/g'- gibt 'abcabcabc ...' aus
John Rix

3
+1 für die Verwendung von keinen Schleifen und nur einem externen Befehl ( tr). Sie können es auch auf so etwas erweitern printf "%${COLUMNS}s\n" | tr " " "=".
Musiphil

2
@ mklement0 Nun, ich hatte gehofft, Sie haben versehentlich die letzte Zeile gezählt wc. Die einzige Schlussfolgerung, die ich daraus ziehen kann, ist " seqsollte nicht verwendet werden".
Camilo Martin

51

Tipp des Hutes an @ gniourf_gniourf für seine Eingabe.

Hinweis: Diese Antwort beantwortet nicht die ursprüngliche Frage, sondern ergänzt die vorhandenen, hilfreichen Antworten durch einen Leistungsvergleich .

Lösungen werden nur hinsichtlich der Ausführungsgeschwindigkeit verglichen - Speicheranforderungen werden nicht berücksichtigt (sie variieren je nach Lösung und können bei großen Wiederholungszahlen von Bedeutung sein).

Zusammenfassung:

  • Wenn Ihre Wiederholungszahl gering ist , z. B. bis zu 100, lohnt es sich, nur Bash-Lösungen zu verwenden , da die Startkosten für externe Dienstprogramme, insbesondere für Perl, von Bedeutung sind.
    • Pragmatisch gesehen sind jedoch möglicherweise alle vorhandenen Lösungen in Ordnung , wenn Sie nur eine Instanz sich wiederholender Zeichen benötigen .
  • Mit großer Anzahl an Wiederholungen , nutzen externe Dienstprogramme , da sie viel schneller sein werden.
    • Vermeiden Sie insbesondere das globale Ersetzen von Teilstrings durch Bash durch große Zeichenfolgen
      (z. B. ${var// /=}), da diese unerschwinglich langsam sind.

Die folgenden Zeitangaben wurden auf einem iMac Ende 2012 mit einer Intel Core i5-CPU mit 3,2 GHz und einem Fusion-Laufwerk unter OSX 10.10.4 und Bash 3.2.57 erstellt und sind durchschnittlich 1000 Läufe.

Die Einträge sind:

  • aufgelistet in aufsteigender Reihenfolge der Ausführungsdauer (schnellste zuerst)
  • vorangestellt mit:
    • M... eine potenziell Multi -Charakter Lösung
    • S... eine Lösung nur für einzelne Zeichen
    • P ... eine POSIX-konforme Lösung
  • gefolgt von einer kurzen Beschreibung der Lösung
  • Suffix mit dem Namen des Autors der ursprünglichen Antwort

  • Kleine Wiederholungszahl: 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
  • Die Nur-Bash-Lösungen sind führend - aber nur mit einer so kleinen Wiederholungszahl! (siehe unten).
  • Die Startkosten für externe Dienstprogramme spielen hier eine Rolle, insbesondere für Perl. Wenn Sie dies in einer Schleife aufrufen müssen - mit kleinen Wiederholungszahlen in jeder Iteration -, vermeiden Sie das Multi-Utility awkund die perlLösungen.

  • Große Wiederholungszahl: 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
  • Die Perl-Lösung aus der Frage ist bei weitem die schnellste.
  • Bashs globaler String-Ersatz ( ${foo// /=}) ist bei großen Saiten unerklärlich unerträglich langsam und wurde aus dem Rennen genommen (dauerte in Bash 4.3.30 ungefähr 50 Minuten (!) Und in Bash 3.2.57 sogar noch länger - ich habe nie darauf gewartet es zu beenden).
  • Bash-Schleifen sind langsam und arithmetische Schleifen ( (( i= 0; ... ))) sind langsamer als geschweifte Klammern ( {1..n}) - obwohl arithmetische Schleifen speichereffizienter sind.
  • awkbezieht sich auf BSD awk (wie auch unter OSX) - es ist merklich langsamer als gawk(GNU Awk) und insbesondere mawk.
  • Beachten Sie, dass mit großen Zahlen und Multi-Char. Strings kann der Speicherverbrauch eine Überlegung sein - die Ansätze unterscheiden sich in dieser Hinsicht.

Hier ist das Bash-Skript ( testrepeat), mit dem das oben genannte erstellt wurde. Es dauert 2 Argumente:

  • die Anzahl der Zeichenwiederholungen
  • optional die Anzahl der durchzuführenden Testläufe und die Berechnung des durchschnittlichen Timings aus

Mit anderen Worten: Die obigen Timings wurden mit testrepeat 100 1000und erhaltentestrepeat 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

Es ist interessant, einen Timing-Vergleich zu sehen, aber ich denke, dass in vielen Programmen die Ausgabe gepuffert ist, so dass ihr Timing geändert werden kann, wenn die Pufferung deaktiviert wurde.
Sergiy Kolodyazhnyy

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

2
Die Perl-Lösung (sid_com) ist also im Grunde die schnellste ... sobald der anfängliche Aufwand für das Starten von Perl erreicht ist. (Es geht von 59 ms für eine kleine Wiederholung auf 67 ms für eine Million Wiederholungen ... also dauerte das Perl-Gabeln ungefähr 59 ms auf Ihrem System)
Olivier Dulac

46

Es gibt mehr als einen Weg, dies zu tun.

Verwenden einer Schleife:

  • Die Klammererweiterung kann mit ganzzahligen Literalen verwendet werden:

    for i in {1..100}; do echo -n =; done    
  • Eine C-ähnliche Schleife ermöglicht die Verwendung von Variablen:

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

Verwenden des printfeingebauten:

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

Wenn Sie hier eine Genauigkeit angeben, wird die Zeichenfolge auf die angegebene Breite ( 0) gekürzt . Da die printfFormatzeichenfolge wiederverwendet wird, um alle Argumente zu verbrauchen, wird dies einfach "="100 Mal gedruckt .

Verwenden von head( printfusw.) und tr:

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

2
++ für die head/ tr-Lösung, die auch bei hohen Wiederholungszahlen gut funktioniert (kleine Einschränkung: head -cist nicht POSIX-kompatibel, aber sowohl BSD als auch GNU headimplementieren sie); Während die anderen beiden Lösungen in diesem Fall langsam sind, haben sie den Vorteil, dass sie auch mit Zeichenfolgen mit mehreren Zeichen arbeiten.
mklement0

1
Verwenden von yesund head- nützlich, wenn Sie eine bestimmte Anzahl von Zeilenumbrüchen wünschen : yes "" | head -n 100. trkann es jedes Zeichen drucken lassen:yes "" | head -n 100 | tr "\n" "="; echo
loxaxs

Etwas überraschend: dd if=/dev/zero count=1 bs=100000000 | tr '\0' '=' >/dev/nullist deutlich langsamer als die head -c100000000 < /dev/zero | tr '\0' '=' >/dev/nullVersion. Natürlich müssen Sie eine Blockgröße von 100M + verwenden, um den Zeitunterschied angemessen zu messen. 100 MByte dauern mit den beiden gezeigten Versionen 1,7 s und 1 s. Ich nahm den tr ab und /dev/nullwarf ihn einfach auf und bekam 0,287 s für die headVersion und 0,675 s für die ddVersion für eine Milliarde Bytes.
Michael Goldshteyn

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

31

Ich habe gerade einen wirklich einfachen Weg gefunden, dies mit seq zu tun:

UPDATE: Dies funktioniert auf dem BSD seq, das mit OS X geliefert wird. YMMV mit anderen Versionen

seq  -f "#" -s '' 10

Druckt '#' 10 Mal wie folgt:

##########
  • -f "#"Legt die Formatzeichenfolge so fest, dass die Zahlen ignoriert werden und nur #für jede gedruckt wird .
  • -s '' Setzt das Trennzeichen auf eine leere Zeichenfolge, um die Zeilenumbrüche zu entfernen, die zwischen jeder Zahl eingefügt werden
  • Die Räume danach -fund -sscheinen wichtig zu sein.

EDIT: Hier ist es in einer praktischen Funktion ...

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

Was du so nennen kannst ...

repeat "#" 10

HINWEIS: Wenn Sie wiederholen, sind #die Anführungszeichen wichtig!


7
Das gibt mir seq: format ‘#’ has no % directive. seqist für Zahlen, nicht für Zeichenfolgen. Siehe gnu.org/software/coreutils/manual/html_node/seq-invocation.html
John B

Ah, also habe ich die BSD-Version von seq unter OS X verwendet. Ich werde die Antwort aktualisieren. Welche Version verwenden Sie?
Sam Salisbury

Ich benutze seq von GNU coreutils.
John B

1
@JohnB: BSD seqwird hier geschickt für die Replikation von Zeichenfolgen verwendet : Die übergebene Formatzeichenfolge , die -fnormalerweise zum Formatieren der generierten Zahlen verwendet wird, enthält nur die Zeichenfolge, die hier repliziert werden soll, sodass die Ausgabe nur Kopien dieser Zeichenfolge enthält. Leider besteht GNU seqdarauf , dass in der Formatzeichenfolge ein Zahlenformat vorhanden ist. Dies ist der Fehler, den Sie sehen.
mklement0

1
Schön gemacht; Funktioniert auch mit Zeichenfolgen mit mehreren Zeichen. Bitte verwenden Sie "$1"(doppelte Anführungszeichen), damit Sie auch Zeichen wie '*'und Zeichenfolgen mit eingebettetem Leerzeichen übergeben können. Wenn Sie es verwenden möchten, müssen %Sie es verdoppeln (andernfalls seqwird es Teil einer Formatspezifikation sein, wie z. B. %f). mit "${1//%/%%}"würde sich darum kümmern. Da Sie (wie bereits erwähnt) BSD verwenden seq , funktioniert dies im Allgemeinen unter BSD-ähnlichen Betriebssystemen (z. B. FreeBSD). Im Gegensatz dazu funktioniert dies nicht unter Linux , wo GNU seq verwendet wird.
mklement0

18

Hier sind zwei interessante Möglichkeiten:

ubuntu @ ubuntu: ~ $ yes = | Kopf -10 | Einfügen -s -d '' -
==========
ubuntu @ ubuntu: ~ $ yes = | Kopf -10 | tr -d "\ n"
========== ubuntu @ ubuntu: ~ $ 

Beachten Sie, dass diese beiden geringfügig voneinander abweichen. - Die pasteMethode endet in einer neuen Zeile. Die trMethode funktioniert nicht.


1
Schön gemacht; Bitte beachten Sie, dass BSD paste unerklärlicherweise die -d '\0'Angabe eines leeren Trennzeichens erfordert und mit -d ''- fehlschlägt - -d '\0'sollte mit allen POSIX-kompatiblen pasteImplementierungen funktionieren und tatsächlich auch mit GNU paste .
mklement0

Ähnlich im Geiste, mit weniger Außenbordwerkzeugen:yes | mapfile -n 100 -C 'printf = \#' -c 1
Bischof

@bishop: Während Ihr Befehl tatsächlich eine Unterschale weniger erstellt, ist er bei höheren Wiederholungszahlen immer noch langsamer, und bei niedrigeren Wiederholungszahlen spielt der Unterschied wahrscheinlich keine Rolle. Der genaue Schwellenwert ist wahrscheinlich sowohl hardware- als auch betriebssystemabhängig. Auf meinem OSX 10.11.5-Computer ist diese Antwort bereits bei 500 schneller. versuche es time yes = | head -500 | paste -s -d '\0' -; time yes | mapfile -n 500 -C 'printf = \#' -c 1. Wichtiger jedoch: Wenn Sie es printftrotzdem verwenden, können Sie auch den einfacheren und effizienteren Ansatz aus der akzeptierten Antwort printf '%.s=' $(seq 500)
wählen

13

Es gibt keinen einfachen Weg. Vermeiden Sie die Verwendung printfund Ersetzung von Schleifen .

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

2
Schön, aber nur mit kleinen Wiederholungszahlen vernünftig. Hier ist ein Funktions-Wrapper repl = 100, der zum Beispiel aufgerufen werden kann (gibt kein Trailing aus \n):repl() { local ts=$(printf "%${2}s"); printf %s "${ts// /$1}"; }
mklement0

1
@ mklement0 Schön, dass Sie Funktionsversionen beider Lösungen bereitstellen, +1 für beide!
Camilo Martin

8

Wenn Sie POSIX-Konformität und -Konsistenz für verschiedene Implementierungen von echound printfund / oder Shells wünschen , die nicht nur 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)

... wird die gleiche Ausgabe wie perl -E 'say "=" x 100'fast überall produzieren.


1
Das Problem ist, dass seqes sich nicht um ein POSIX-Dienstprogramm handelt (obwohl BSD- und Linux-Systeme Implementierungen davon haben) - Sie können POSIX-Shell-Arithmetik whilestattdessen mit einer Schleife ausführen, wie in der Antwort von @ Xennex81 (mit printf "=", wie Sie richtig vorschlagen, anstatt echo -n).
mklement0

1
Ups, du hast ganz recht. Solche Dinge rutschen manchmal einfach an mir vorbei, da dieser Standard keinen Sinn ergibt. calist POSIX. seqist nicht. Anstatt die Antwort mit einer while-Schleife neu zu schreiben (wie Sie sagen, ist dies bereits in anderen Antworten enthalten), werde ich eine RYO-Funktion hinzufügen. Auf diese Weise lehrreicher ;-).
Geoff Nixon

8

Die Frage war, wie es geht echo:

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

Dies wird genau das Gleiche tun, perl -E 'say "=" x 100'aber echonur mit.


Das ist ungewöhnlich, wenn Sie keine zusätzlichen Leerzeichen darin einfügen oder es mit folgenden Elementen bereinigen: echo -e $ _ {1..100} '\ b =' | col
anthony

1
Schlechte Idee. Dies schlägt fehl, wenn $_1, $_2oder eine andere der hundert Variablen Werte hat.
John Kugelman

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

Das ist eklig . Ich liebe es: D
dimo414

6

Ein reiner Bash-Weg ohne eval, ohne Unterschalen, ohne externe Werkzeuge, ohne Klammererweiterungen (dh Sie können die Zahl in einer Variablen wiederholen lassen):

Wenn Sie eine Variable erhalten n, die zu einer (nicht negativen) Zahl und einer Variablen erweitert wird pattern, z.

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

Sie können eine Funktion damit erstellen:

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

Mit diesem Set:

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

Für diesen kleinen Trick verwenden wir printfziemlich viel mit:

  • -v varname: Anstatt auf Standardausgabe zu drucken, printfwird der Inhalt der formatierten Zeichenfolge in eine Variable eingefügt varname.
  • '% * s': printfverwendet das Argument, um die entsprechende Anzahl von Leerzeichen zu drucken. ZB printf '%*s' 42werden 42 Leerzeichen gedruckt.
  • Wenn wir schließlich die gewünschte Anzahl von Leerzeichen in unserer Variablen haben, verwenden wir eine Parametererweiterung, um alle Leerzeichen durch unser Muster zu ersetzen: ${var// /$pattern}Wird auf die Erweiterung von erweitert, varwobei alle Leerzeichen durch die Erweiterung von ersetzt werden $pattern.

Sie können die tmpVariable in der repeatFunktion auch durch indirekte Erweiterung entfernen:

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

Interessante Variation für die Übergabe des Variablennamens. Während diese Lösung für Wiederholungszählungen bis zu etwa 1.000 geeignet ist (und daher wahrscheinlich für die meisten realen Anwendungen geeignet ist, wenn ich raten sollte), wird sie für höhere Zählungen sehr langsam (siehe weiter unten) Kommentar).
mklement0

Es scheint, dass bashdie globalen Zeichenfolgenersetzungsvorgänge im Kontext der Parametererweiterung ( ${var//old/new}) besonders langsam sind: unerträglich langsam in Bash 3.2.57und langsam in Bash 4.3.30, zumindest auf meinem OSX 10.10.3-System auf einem 3,2-GHz-Intel Core i5-Computer: Mit Bei einer Zählung von 1.000 sind die Dinge langsam ( 3.2.57) / schnell ( 4.3.30): 0,1 / 0,004 Sekunden. Das Erhöhen der Anzahl auf 10.000 ergibt auffallend unterschiedliche Zahlen: repeat 10000 = vardauert ungefähr 80 Sekunden (!) In Bash 3.2.57und ungefähr 0,3 Sekunden in Bash 4.3.30(viel schneller als eingeschaltet 3.2.57, aber immer noch langsam).
mklement0

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

Oder

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

Beispiel


3
Schön gemacht; Dies ist POSIX-konform und auch bei hohen Wiederholungszahlen relativ schnell, unterstützt aber auch Zeichenfolgen mit mehreren Zeichen. Hier ist die Shell-Version : awk 'BEGIN { while (c++ < 100) printf "=" }'. Eingewickelt in eine parametrisierte Shell-Funktion ( repeat 100 =z. B. aufrufen als ) : repeat() { awk -v count="$1" -v txt=".$2" 'BEGIN { txt=substr(txt, 2); while (i++ < count) printf txt }'; }. (Das Dummy- .Präfix char und der komplementäre substrAufruf werden benötigt, um einen Fehler in BSD zu awk=
umgehen

1
Die NF = 100Lösung ist sehr clever (obwohl Sie 100 verwenden =müssen , um 100 zu erhalten NF = 101). Die Einsprüche sind , dass es abstürzt BSD awk(aber es ist sehr schnell mit gawkund noch schneller mit mawk), und das POSIX bespricht weder die Zuordnung zu NF, noch die Verwendung von Feldern in BEGINBlöcken. Sie können es auch in BSD awkmit einer kleinen Änderung zum Laufen bringen: awk 'BEGIN { OFS = "="; $101=""; print }'(aber seltsamerweise ist das in BSD awknicht schneller als die Loop-Lösung). Als parametrisierte Shell-Lösung : repeat() { awk -v count="$1" -v txt=".$2" 'BEGIN { OFS=substr(txt, 2); $(count+1)=""; print }'; }.
mklement0

Hinweis für Benutzer - Der Trick NF = 100 verursacht einen Segmentfehler bei älteren awk. Das original-awkist der Name , unter Linux des älteren awk ähnlich wie BSDs awk, die auch zum Absturz berichtet wurde, wenn Sie dies versuchen. Beachten Sie, dass Abstürze normalerweise der erste Schritt sind, um einen ausnutzbaren Fehler zu finden. Diese Antwort fördert so unsicheren Code.

2
Hinweis für Benutzer - original-awkist nicht Standard und nicht empfohlen
Steven Penny

Eine Alternative zum ersten Code-Snippet kann awk NF=100 OFS='=' <<< ""(using bashand gawk)
oliv

4

Ich denke, der ursprüngliche Zweck der Frage war es, dies nur mit den eingebauten Befehlen der Shell zu tun. So forSchleifen und printflegitimen wären, während rep, perlund auch jotwürde unten nicht. Trotzdem der folgende Befehl

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

Druckt beispielsweise eine fensterweite Zeile von \/\/\/\/\/\/\/\/\/\/\/\/


2
Schön gemacht; Dies funktioniert auch bei hohen Wiederholungszahlen (und unterstützt auch Zeichenfolgen mit mehreren Zeichen). Um den Ansatz besser zu veranschaulichen, ist hier das Äquivalent des OP-Befehls : jot -s '' -b '=' 100. Der Nachteil ist , dass während BSD-ähnlichen Plattformen, einschließlich OSX, komm mit jot, Linux - Distributionen nicht .
mklement0

1
Danke, ich mag deine Verwendung von -s '' noch besser. Ich habe meine Skripte geändert.
Stefan Ludwig

Auf neueren Debian- basierten Systemen apt install athena-jotwürde bieten jot.
Agc

4

Wie andere bereits gesagt haben, geht die Erweiterung der Bash- Klammern der Parametererweiterung voraus , sodass Bereiche nur Literale enthalten können. und bieten saubere Lösungen, sind jedoch nicht vollständig von einem System auf ein anderes portierbar, selbst wenn Sie auf jedem dieselbe Shell verwenden. (Obwohl zunehmend verfügbar ist; z. B. in FreeBSD 9.3 und höher .){m,n}seqjotseqeval Und andere Formen der Indirektion funktionieren immer, sind jedoch etwas unelegant.

Glücklicherweise unterstützt bash den C-Stil für Schleifen (nur mit arithmetischen Ausdrücken). Also hier ist ein prägnanter "Pure Bash" Weg:

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

Dies nimmt die Anzahl der Wiederholungen als erstes Argument und die zu wiederholende Zeichenfolge (die wie in der Problembeschreibung ein einzelnes Zeichen sein kann) als zweites Argument. repecho 7 bAusgänge bbbbbbb(durch einen Zeilenumbruch abgeschlossen).

Dennis Williamson gab im Wesentlichen diese Lösung vor vier Jahren in seiner ausgezeichneten Antwort auf das Erstellen einer Reihe von wiederholten Zeichen in Shell-Schrift . Mein Funktionskörper unterscheidet sich geringfügig vom dortigen Code:

  • Da der Fokus hier auf der Wiederholung eines einzelnen Zeichens liegt und die Shell Bash ist, ist es wahrscheinlich sicher, sie echoanstelle von zu verwenden printf. Und ich habe die Problembeschreibung in dieser Frage als Ausdruck einer Präferenz zum Drucken gelesen echo. Die obige Funktionsdefinition funktioniert in bash und ksh93 . Obwohl printfes portabler ist (und normalerweise für solche Dinge verwendet werden sollte), ist echodie Syntax wahrscheinlich besser lesbar.

    Die echoeingebauten Shells einiger Shells werden -von sich aus als Option interpretiert - obwohl die übliche Bedeutung von -stdin für die Eingabe unsinnig ist echo. zsh macht das. Und es gibt definitiv echos, die nicht erkennen -n, da es nicht Standard ist . (Viele Muscheln im Bourne-Stil akzeptieren C-Stil für Schleifen überhaupt nicht, daher muss ihr echoVerhalten nicht berücksichtigt werden.)

  • Hier besteht die Aufgabe darin, die Sequenz zu drucken; dort sollte es einer Variablen zugewiesen werden.

Wenn $ndie gewünschte Anzahl von Wiederholungen vorliegt und Sie sie nicht wiederverwenden müssen und etwas noch kürzeres wünschen:

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

nmuss eine Variable sein - funktioniert auf diese Weise nicht mit Positionsparametern. $sist der zu wiederholende Text.


2
Vermeiden Sie unbedingt Loop-Versionen. printf "%100s" | tr ' ' '='ist optimal.
ocodo

Gute Hintergrundinformationen und ein großes Lob für das Verpacken der Funktionalität als Funktion, die zshübrigens auch funktioniert . Der Echo-in-a-Loop-Ansatz funktioniert gut für kleinere Wiederholungszahlen, aber für größere gibt es POSIX-kompatible Alternativen, die auf Dienstprogrammen basieren , wie der Kommentar von @ Slomojo zeigt.
mklement0

Das Hinzufügen von Klammern um Ihre kürzere Schleife (while ((n--)); do echo -n "$s"; done; echo)

benutze printf statt echo! es ist viel portabler (echo -n kann nur auf einigen Systemen funktionieren). siehe unix.stackexchange.com/questions/65803/… (eine der großartigen Antworten von Stephane Chazelas)
Olivier Dulac

@OlivierDulac Die Frage hier ist über Bash. Unabhängig davon , welches Betriebssystem Sie ausführen, wenn Sie bash verwenden , verfügt bash über ein integriertes echoSystem, das dies unterstützt -n. Der Geist dessen, was Sie sagen, ist absolut richtig. printfsollte fast immer bevorzugt werden echo, zumindest bei nicht interaktiver Verwendung. Aber ich denke nicht, dass es in irgendeiner Weise unangemessen oder irreführend war, eine echoAntwort auf eine Frage zu geben , die nach einer Frage fragte und die genügend Informationen lieferte, um zu wissen, dass es funktionieren würde . Bitte beachten Sie auch, dass die Unterstützung für ((n--))(ohne a $) selbst von POSIX nicht garantiert wird.
Eliah Kagan

4

Python ist allgegenwärtig und funktioniert überall gleich.

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

Zeichen und Anzahl werden als separate Parameter übergeben.


Ich denke, das war die Absicht hierpython -c "import sys; print(sys.argv[1] * int(sys.argv[2]))" "=" 100
Gazhay

@loevborg ist das nicht ein bisschen weit hergeholt?
Sapphire_Brick

3

In Bash 3.0 oder höher

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

3

Ein weiteres Mittel, um eine beliebige Zeichenfolge n-mal zu wiederholen:

Vorteile:

  • Funktioniert mit der POSIX-Shell.
  • Die Ausgabe kann einer Variablen zugewiesen werden.
  • Wiederholt eine beliebige Zeichenfolge.
  • Sehr schnell auch bei sehr großen Wiederholungen.

Nachteile:

  • Benötigt den yesBefehl von Gnu Core Utils .
#!/usr/bin/sh
to_repeat='='
repeat_count=80
yes "$to_repeat" | tr -d '\n' | head -c "$repeat_count"

Mit einem ANSI-Terminal und zu wiederholenden US-ASCII-Zeichen. Sie können eine ANSI-CSI-Escape-Sequenz verwenden. Dies ist der schnellste Weg, um ein Zeichen zu wiederholen.

#!/usr/bin/env bash

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

Oder statisch:

Drucken Sie eine Zeile 80 Mal =:

printf '=\e[80b\n'

Einschränkungen:

  • Nicht alle Terminals verstehen die repeat_charANSI-CSI-Sequenz.
  • Es können nur US-ASCII- oder Einzelbyte-ISO-Zeichen wiederholt werden.
  • Wiederholen Sie die Stopps in der letzten Spalte, damit Sie einen großen Wert verwenden können, um eine ganze Zeile unabhängig von der Terminalbreite zu füllen.
  • Die Wiederholung dient nur zur Anzeige. Durch das Erfassen der Ausgabe in einer Shell-Variablen wird die repeat_charANSI-CSI-Sequenz nicht auf das wiederholte Zeichen erweitert.

1
Kleiner Hinweis - REP (CSI b) sollte normal umlaufen, wenn sich das Terminal im Umhüllungsmodus befindet.
Jerch

3

Folgendes verwende ich, um unter Linux eine Zeichenzeile über den Bildschirm zu drucken (basierend auf der Terminal- / Bildschirmbreite).

Drucken Sie "=" über den Bildschirm:

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

Erläuterung:

Drucken Sie ein Gleichheitszeichen so oft wie in der angegebenen Reihenfolge:

printf '=%.0s' #sequence

Verwenden Sie die Ausgabe eines Befehls (dies ist eine Bash-Funktion namens Command Substitution):

$(example_command)

Geben Sie eine Sequenz an, ich habe 1 bis 20 als Beispiel verwendet. Im letzten Befehl wird der Befehl tput anstelle von 20 verwendet:

seq 1 20

Geben Sie die Anzahl der aktuell im Terminal verwendeten Spalten an:

tput cols


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

2

Am einfachsten ist es, diesen Einzeiler in csh / tcsh zu verwenden:

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


2

Eine elegantere Alternative zur vorgeschlagenen Python-Lösung könnte sein:

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

1

Wenn Sie ein Zeichen n-mal wiederholen möchten, wobei es na VARIABLE ist, hängt dies beispielsweise von der Länge einer Zeichenfolge ab, die Sie ausführen können:

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

Es zeigt an:

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

lengthwird nicht funktionieren expr, meinten Sie wahrscheinlich n=$(expr 10 - ${#vari}); Es ist jedoch einfacher und effizienter, die arithmetische Erweiterung von Bash zu verwenden : n=$(( 10 - ${#vari} )). Im Mittelpunkt Ihrer Antwort steht auch der Perl-Ansatz, zu dem das OP nach einer Bash- Alternative sucht .
mklement0

1

Dies ist die längere Version dessen, wofür Eliah Kagan eintrat:

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

Natürlich können Sie auch printf dafür verwenden, aber nicht wirklich nach meinem Geschmack:

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

Diese Version ist Dash-kompatibel:

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

mit i ist die anfängliche Nummer.


In Bash und mit einem positiven n: while (( i-- )); do echo -n " "; donefunktioniert.

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
}

Probeläufe

$ repeatString 'a1' 10 
a1a1a1a1a1a1a1a1a1a1

$ repeatString 'a1' 0 

$ repeatString '' 10 

Referenzbibliothek unter: https://github.com/gdbtek/linux-cookbooks/blob/master/libraries/util.bash


1

Wie könnte ich das mit Echo machen?

Sie können dies tun, echowenn auf echoFolgendes folgt sed:

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

Eigentlich ist das echodort unnötig.


1

Meine Antwort ist etwas komplizierter und wahrscheinlich nicht perfekt, aber für diejenigen, die große Zahlen ausgeben möchten, konnte ich in 3 Sekunden etwa 10 Millionen erledigen.

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

Am einfachsten ist es, diesen Einzeiler in Bash zu verwenden:

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


1

Eine andere Möglichkeit besteht darin, GNU seq zu verwenden und alle generierten Zahlen und Zeilenumbrüche zu entfernen:

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

Dieser Befehl druckt das #Zeichen 100 Mal.


1

Die meisten existierenden Lösungen hängen alle auf {1..10}Syntax Unterstützung der Schale, die ist bash- und zsh- spezifisch und funktioniert nicht in tcshoder OpenBSD kshund die meisten Nicht-bashsh .

Das Folgende sollte unter OS X und allen * BSD-Systemen in jeder Shell funktionieren. Tatsächlich kann damit eine ganze Matrix verschiedener Arten von dekorativen Räumen erzeugt werden:

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

Leider erhalten wir keine nachgestellte Newline. die printf '\n'nach der Falte durch ein Extra repariert werden kann:

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

Verweise:


Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.