Come posso ripetere un personaggio in Bash?


240

Come potrei farlo con echo?

perl -E 'say "=" x 100'

Purtroppo questo non è Bash.
solidsnack,

1
non con l'eco, ma sullo stesso argomento ruby -e 'puts "=" * 100'opython -c 'print "=" * 100'
Evgeny

1
Ottima domanda Risposte molto buone Ho usato una delle risposte in un vero lavoro qui, che posterò come esempio: github.com/drbeco/oldfiles/blob/master/oldfiles (usato printfcon seq)svrb=`printf '%.sv' $(seq $vrb)`
Dr Beco,

Una soluzione generica per stampare qualunque (1 o più caratteri, anche con nuove righe): Repeat_this () {i = 1; mentre ["$ i" -le "$ 2"]; esegui printf "% s" "$ 1"; i = $ (($ i + 1)); fatto ; printf '\ n';}. Usa in questo modo: Repeat_this "qualcosa" Number_of_repetitions. Ad esempio, per mostrare la ripetizione di 5 volte qualcosa tra cui 3 newline: Repeat_this "$ (printf '\ n \ n \ nthis')" 5. L'ultimo printf '\ n' può essere rimosso (ma l'ho inserito per creare file di testo, e quelli hanno bisogno di una nuova riga come ultimo personaggio!)
Olivier Dulac,

Risposte:


396

Puoi usare:

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

Come funziona:

Bash espande {1..100} in modo che il comando diventi:

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

Ho impostato il formato di printf sul =%.0squale significa che verrà sempre stampato un singolo, =indipendentemente dall'argomento che viene fornito. Pertanto stampa 100 =s.


14
Ottima soluzione che funziona abbastanza bene anche con conteggi ripetuti di grandi dimensioni. Ecco un wrapper di funzione con cui puoi invocare repl = 100, ad esempio ( evalè necessario un trucco, purtroppo, per basare l'espansione del controvento su una variabile):repl() { printf "$1"'%.s' $(eval "echo {1.."$(($2))"}"); }
mklement0

7
È possibile impostare il limite superiore usando un var? Ho provato e non riesco a farlo funzionare.
Mike Purcell,

70
Non è possibile utilizzare le variabili all'interno dell'espansione del controvento. Usa seqinvece ad es $(seq 1 $limit).
dogbane,

11
Se funzionalizzare questo è meglio riorganizzare da $s%.0sal %.0s$scontrario trattini causare un printferrore.
KomodoDave,

5
Questo mi ha fatto notare un comportamento di Bash printf: continua ad applicare la stringa di formato fino a quando non rimangono più argomenti. Avevo assunto che elaborasse la stringa di formato solo una volta!
Jeenu,

89

Nessun modo semplice. Ma per esempio:

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

O forse un modo conforme agli standard:

printf %100s |tr " " "="

C'è anche un tput rep, ma per quanto riguarda i miei terminali a portata di mano (xterm e linux) non sembrano supportarlo :)


3
Nota che la prima opzione con seq ne stampa uno in meno del numero indicato, quindi quell'esempio stamperà 99 =caratteri.
Camilo Martin,

13
printf trè l'unica soluzione POSIX, perché seq, yese {1..3}non sono POSIX.
Ciro Santilli 10 冠状 病 六四 事件 法轮功

2
Per ripetere una stringa anziché un solo carattere: printf %100s | sed 's/ /abc/g'- genera "abcabcabc ..."
John Rix,

3
+1 per l'utilizzo di nessun loop e solo un comando esterno ( tr). Potresti anche estenderlo a qualcosa del genere printf "%${COLUMNS}s\n" | tr " " "=".
musiphil,

2
@ mklement0 Beh, speravo che avessi contato l'ultima riga per errore wc. L'unica conclusione che posso trarre da questo è " seqnon dovrebbe essere usato".
Camilo Martin,

51

Punta del cappello a @ gniourf_gniourf per il suo contributo.

Nota: questa risposta non risponde alla domanda originale, ma integra le risposte esistenti e utili confrontando le prestazioni .

Le soluzioni vengono confrontate solo in termini di velocità di esecuzione - i requisiti di memoria non vengono presi in considerazione (variano in base alle soluzioni e possono avere un numero elevato di ripetizioni).

Sommario:

  • Se il conteggio delle ripetizioni è piccolo , diciamo fino a circa 100, vale la pena andare con le soluzioni solo Bash , poiché il costo di avvio delle utility esterne è importante, specialmente di Perl.
    • In termini pragmatici, tuttavia, se è necessaria solo un'istanza di caratteri ripetuti, tutte le soluzioni esistenti potrebbero andare bene.
  • Con un numero elevato di ripetizioni , utilizza le utility esterne , poiché saranno molto più veloci.
    • In particolare, evita la sostituzione globale della sottostringa di Bash con stringhe di grandi dimensioni
      (ad es. ${var// /=}), Poiché è proibitivamente lento.

Di seguito sono riportati i tempi presi su un iMac di fine 2012 con una CPU Intel Core i5 da 3,2 GHz e un Fusion Drive, con OSX 10.10.4 e bash 3.2.57, e sono la media di 1000 corse.

Le voci sono:

  • elencati in ordine crescente di durata dell'esecuzione (prima il più veloce)
  • con il prefisso:
    • M... una soluzione potenzialmente multi- carattere
    • S... una soluzione a singolo carattere
    • P ... una soluzione conforme a POSIX
  • seguita da una breve descrizione della soluzione
  • suffisso con il nome dell'autore della risposta originaria

  • Numero di ripetizioni piccolo: 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
  • Le soluzioni solo Bash guidano il pacchetto - ma solo con una ripetizione conta così piccolo! (vedi sotto).
  • Il costo di avvio delle utility esterne è importante qui, in particolare Perl. Se è necessario chiamare questo in un ciclo - con piccoli conteggi di ripetizione in ogni iterazione - evitare la multiutility awke le perlsoluzioni.

  • Grande numero di ripetizioni: 1000000 (1 milione)
[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 soluzione Perl della domanda è di gran lunga la più veloce.
  • La sostituzione globale di stringhe ( ${foo// /=}) di Bash è inspiegabilmente lenta con stringhe di grandi dimensioni, ed è stata rimossa dalla corsa (impiegata circa 50 minuti (!) In Bash 4.3.30 e ancora più lunga in Bash 3.2.57 - Non ho mai aspettato per finire).
  • I loop di Bash sono lenti e i loop aritmetici ( (( i= 0; ... ))) sono più lenti di quelli espansi con parentesi graffe ( {1..n}) - sebbene i loop aritmetici siano più efficienti in termini di memoria.
  • awksi riferisce a BSD awk (come si trova anche su OSX) - è notevolmente più lento di gawk(GNU Awk) e soprattutto mawk.
  • Si noti che con grandi conteggi e caratteri multipli. stringhe, il consumo di memoria può diventare una considerazione - gli approcci differiscono in questo senso.

Ecco lo script Bash ( testrepeat) che ha prodotto quanto sopra. Sono necessari 2 argomenti:

  • il conteggio delle ripetizioni del personaggio
  • facoltativamente, il numero di esecuzioni di test da eseguire e da cui calcolare il tempo medio

In altre parole: i tempi sopra sono stati ottenuti con testrepeat 100 1000etestrepeat 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

È interessante vedere il confronto dei tempi, ma penso che in molti programmi l'output sia bufferizzato, quindi i loro tempi possono essere modificati se il buffering era disattivato.
Sergiy Kolodyazhnyy,

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

2
Quindi la soluzione perl (sid_com) è sostanzialmente la più veloce ... una volta raggiunto l'overhead iniziale del lancio di perl. (passa da 59ms per una piccola ripetizione a 67ms per un milione di ripetizioni ... quindi il fork di perl ha impiegato circa 59ms sul tuo sistema)
Olivier Dulac

46

C'è più di un modo per farlo.

Utilizzando un loop:

  • L'espansione del controvento può essere utilizzata con valori letterali interi:

    for i in {1..100}; do echo -n =; done    
  • Un ciclo di tipo C consente l'uso di variabili:

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

Utilizzando il printfbuilt-in:

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

Specificando una precisione qui viene troncata la stringa per adattarla alla larghezza specificata ( 0). Poiché printfriutilizza la stringa di formato per consumare tutti gli argomenti, questa viene semplicemente stampata "="100 volte.

Utilizzando head( printf, ecc.) E tr:

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

2
++ per la soluzione head/ tr, che funziona bene anche con conteggi ripetuti elevati (piccolo avvertimento: head -cnon è conforme a POSIX, ma sia BSD che GNU la headimplementano); mentre le altre due soluzioni saranno lente in quel caso, hanno anche il vantaggio di lavorare con stringhe multi- carattere.
mklement0

1
Utilizzando yese head- utile se si desidera un certo numero di nuove righe: yes "" | head -n 100. trpuò farlo stampare qualsiasi personaggio:yes "" | head -n 100 | tr "\n" "="; echo
loxaxs,

Un po 'sorprendentemente: dd if=/dev/zero count=1 bs=100000000 | tr '\0' '=' >/dev/nullè significativamente più lento della head -c100000000 < /dev/zero | tr '\0' '=' >/dev/nullversione. Ovviamente devi usare una dimensione del blocco di 100 M + per misurare ragionevolmente la differenza di tempo. 100 M byte richiedono 1,7 se 1 s con le due rispettive versioni mostrate. Ho tolto il tr e l'ho appena scaricato /dev/nulle ho ottenuto 0,287 s per la headversione e 0,675 s per la ddversione per un miliardo di byte.
Michael Goldshteyn,

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

31

Ho appena trovato un modo davvero semplice per farlo usando seq:

AGGIORNAMENTO: funziona con BSD seqfornito con OS X. YMMV con altre versioni

seq  -f "#" -s '' 10

Stampa '#' 10 volte, in questo modo:

##########
  • -f "#"imposta la stringa di formato per ignorare i numeri e stampare solo #per ognuno.
  • -s '' imposta il separatore su una stringa vuota per rimuovere le nuove righe che seq inserisce tra ciascun numero
  • Gli spazi dopo -fe -ssembrano essere importanti.

EDIT: qui è in una comoda funzione ...

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

Che puoi chiamare così ...

repeat "#" 10

NOTA: se stai ripetendo #le virgolette sono importanti!


7
Questo mi dà seq: format ‘#’ has no % directive. seqè per i numeri, non per le stringhe. Vedi gnu.org/software/coreutils/manual/html_node/seq-invocation.html
John B

Ah, quindi stavo usando la versione BSD di seq trovata su OS X. Aggiornerò la risposta. Che versione stai usando?
Sam Salisbury,

Sto usando seq da GNU coreutils.
John B,

1
@JohnB: BSD seqviene abilmente riproposto qui per replicare le stringhe : la stringa di formato passata a -f- normalmente usata per formattare i numeri generati - contiene solo la stringa da replicare qui in modo che l'output contenga solo copie di quella stringa. Sfortunatamente, GNU seqinsiste sulla presenza di un formato numerico nella stringa di formato, che è l'errore che stai vedendo.
mklement0

1
Ben fatto; funziona anche con stringhe con più caratteri. Utilizzare "$1"(virgolette doppie), in modo da poter passare anche caratteri come '*'e stringhe con spazi bianchi incorporati. Infine, se vuoi essere in grado di usarlo %, devi raddoppiarlo (altrimenti seqpenserai che sia parte di una specifica di formato come %f); l'uso se "${1//%/%%}"ne occuperebbe. Dal momento che (come dici tu) stai usando BSD seq , questo funzionerà su sistemi operativi simili a BSD in generale (ad esempio, FreeBSD) - al contrario, non funzionerà su Linux , dove GNU seq è usato.
mklement0

18

Ecco due modi interessanti:

ubuntu @ ubuntu: ~ $ yes = | testa -10 | incolla -s -d '' -
==========
ubuntu @ ubuntu: ~ $ yes = | testa -10 | tr -d "\ n"
========== ubuntu @ ubuntu: ~ $ 

Nota che questi due sono leggermente diversi: il pastemetodo termina in una nuova riga. Il trmetodo no.


1
Ben fatto; si prega di notare che BSD paste richiede inspiegabilmente -d '\0'per specificare un delimitatore vuoto e non riesce con -d ''- -d '\0'dovrebbe funzionare con tutte le pasteimplementazioni compatibili con POSIX e in effetti funziona anche con GNU paste .
mklement0

Simile nello spirito, con meno strumenti fuoribordo:yes | mapfile -n 100 -C 'printf = \#' -c 1
vescovo

@bishop: Sebbene il tuo comando crei effettivamente una subshell in meno, è ancora più lento per conteggi ripetuti più alti e per conteggi ripetuti più bassi la differenza probabilmente non ha importanza; la soglia esatta dipende probabilmente sia dall'hardware che dal sistema operativo, ad esempio sulla mia macchina OSX 10.11.5 questa risposta è già più veloce a 500; provare time yes = | head -500 | paste -s -d '\0' -; time yes | mapfile -n 500 -C 'printf = \#' -c 1. Ancora più importante, tuttavia: se si utilizza printfcomunque, è possibile anche seguire l'approccio più semplice ed efficiente dalla risposta accettata:printf '%.s=' $(seq 500)
mklement0

13

Non esiste un modo semplice. Evitare l'uso printfe la sostituzione di loop .

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

2
Bello, ma funziona solo ragionevolmente con piccoli conteggi ripetuti. Ecco un wrapper di funzione che può essere invocato come repl = 100, ad esempio (non \nrepl() { local ts=$(printf "%${2}s"); printf %s "${ts// /$1}"; }
genera

1
@ mklement0 Gentile da parte sua fornire versioni di funzione di entrambe le soluzioni, +1 su entrambe!
Camilo Martin,

8

Se si desidera la conformità POSIX e la coerenza tra diverse implementazioni di echoe printf, e / o shell diverse dal solo 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)

... produrrà lo stesso output di perl -E 'say "=" x 100'quasi ovunque.


1
Il problema è che seqnon si tratta di un'utilità POSIX (sebbene i sistemi BSD e Linux ne abbiano implementazioni) - puoi invece eseguire l'aritmetica della shell POSIX con un whileciclo, come nella risposta di @ Xennex81 (con printf "=", come giustamente suggerito, piuttosto che echo -n).
mklement0

1
Oops, hai ragione. A volte cose del genere mi sfuggono perché lo standard non ha senso. calè POSIX. seqnon è. Comunque, piuttosto che riscrivere la risposta con un ciclo while (come dici tu, che è già in altre risposte) aggiungerò una funzione RYO. Più educativo in quel modo ;-).
Geoff Nixon,

8

La domanda era su come farlo con echo:

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

Questo farà esattamente lo stesso perl -E 'say "=" x 100'ma echosolo con .


Ora, questo è insolito, se non ci metti spazio extra in backspaces o pulisci usando: echo -e $ _ {1..100} '\ b =' | col
anthony

1
Cattiva idea. Ciò fallirà se $_1, $_2o qualsiasi altra delle cento variabili ha valori.
John Kugelman,

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

Questo è disgustoso . Lo adoro: D
dimo414

6

Un modo puro di Bash senza no eval, subshells, senza strumenti esterni, senza espansioni di parentesi graffe (cioè, puoi avere il numero da ripetere in una variabile):

Se ti viene data una variabile nche si espande in un numero (non negativo) e una variabile pattern, ad es.

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

Puoi fare una funzione con questo:

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

Con questo set:

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

Per questo piccolo trucco stiamo usando printfparecchio con:

  • -v varname: anziché stampare sull'output standard, printfil contenuto della stringa formattata verrà modificato in variabile varname.
  • '% * s': printfutilizzerà l'argomento per stampare il numero corrispondente di spazi. Ad esempio, printf '%*s' 42stamperà 42 spazi.
  • Infine, quando abbiamo il numero desiderato di spazi nella nostra variabile, utilizziamo un'espansione dei parametri per sostituire tutti gli spazi con il nostro modello: ${var// /$pattern}si espanderà all'espansione di varcon tutti gli spazi sostituiti dall'espansione di $pattern.

Puoi anche eliminare la tmpvariabile nella repeatfunzione usando l'espansione indiretta:

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

Variazione interessante per passare il nome della variabile. Mentre questa soluzione va bene per i conteggi ripetuti fino a circa 1.000 (e quindi probabilmente per la maggior parte delle applicazioni della vita reale, se dovessi indovinare), diventa molto lento per i conteggi più alti (vedi dopo commento).
mklement0

Sembra che bashle operazioni di sostituzione globale delle stringhe nel contesto dell'espansione dei parametri ( ${var//old/new}) siano particolarmente lente: estremamente lento in bash 3.2.57e lento in bash 4.3.30, almeno sul mio sistema OSX 10.10.3 su una macchina Intel Core i5 da 3,2 Ghz: Con un conteggio di 1.000, le cose sono lente ( 3.2.57) / veloci ( 4.3.30): 0,1 / 0,004 secondi. Aumentare il conteggio a 10.000 produce numeri sorprendentemente diversi: repeat 10000 = varimpiega circa 80 secondi (!) In bash 3.2.57e circa 0,3 secondi in bash 4.3.30(molto più veloce che in attivo 3.2.57, ma comunque lento).
mklement0

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

O

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

Esempio


3
Ben fatto; questo è conforme a POSIX e ragionevolmente veloce anche con conteggi ripetuti elevati, supportando anche stringhe multi-carattere. Ecco la versione della shell: awk 'BEGIN { while (c++ < 100) printf "=" }'. Avvolto in una funzione shell parametrizzata (invoke come repeat 100 =, per esempio): repeat() { awk -v count="$1" -v txt=".$2" 'BEGIN { txt=substr(txt, 2); while (i++ < count) printf txt }'; }. (Il .prefisso fittizio char e la substrchiamata complementare sono necessari per aggirare un bug in BSD awk, dove passare un valore variabile che inizia con =interrompe il comando.)
mklement0

1
La NF = 100soluzione è molto intelligente (anche se per ottenere 100 =, è necessario utilizzare NF = 101). Gli avvertimenti sono che si blocca BSD awk(ma è molto veloce con gawke ancora più veloce con mawk), e che discute POSIX né assegnazione di NF, né l'uso di campi in BEGINblocchi. Puoi farlo funzionare anche in BSD awkcon una leggera modifica: awk 'BEGIN { OFS = "="; $101=""; print }'(ma curiosamente, in BSD awkche non è più veloce della soluzione loop). Come soluzione shell parametri: repeat() { awk -v count="$1" -v txt=".$2" 'BEGIN { OFS=substr(txt, 2); $(count+1)=""; print }'; }.
mklement0

Nota per gli utenti: il trucco NF = 100 provoca un errore di segmento nel vecchio awk. Il original-awkè il nome sotto Linux del awk anziani simile a awk di BSD, che è stata riportata anche in crash, se si vuole provare questo. Si noti che l'arresto anomalo di solito è il primo passo verso la ricerca di un bug sfruttabile. Questa risposta sta promuovendo così un codice insicuro.

2
Nota per gli utenti - original-awknon è standard e non è raccomandato
Steven Penny

Un'alternativa al primo frammento di codice può essere awk NF=100 OFS='=' <<< ""(usando bashe gawk)
oliv

4

Immagino che lo scopo originale della domanda fosse quello di farlo solo con i comandi integrati della shell. Così forloop e printfs sarebbe legittimo, mentre rep, perle anche jotal di sotto no. Tuttavia, il seguente comando

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

ad esempio, stampa una riga di \/\/\/\/\/\/\/\/\/\/\/\/


2
Ben fatto; funziona bene anche con conteggi ripetuti elevati (supportando anche stringhe multi-carattere). Per meglio illustrare l'approccio, ecco l'equivalente del comando del PO: jot -s '' -b '=' 100. L'avvertenza è che mentre le piattaforme simili a BSD, incluso OSX, arrivano jot, le distribuzioni Linux no .
mklement0

1
Grazie, mi piace il tuo uso di -s '' ancora meglio. Ho cambiato i miei script.
Stefan Ludwig,

Sui recenti sistemi basati su Debian , apt install athena-jotfornirebbe jot.
agc,

4

Come altri hanno già detto, in bash l' espansione del parentesi precede l' espansione dei parametri , quindi gli intervalli possono contenere solo valori letterali. e fornire soluzioni pulite ma non sono completamente portatili da un sistema all'altro, anche se si utilizza la stessa shell su ciascuno. (Anche se è sempre più disponibile; ad esempio, in FreeBSD 9.3 e versioni successive .) E altre forme di indiretta funzionano sempre ma sono in qualche modo ineleganti.{m,n}seqjotseqeval

Fortunatamente, bash supporta lo stile C per i loop (solo con espressioni aritmetiche). Quindi ecco un modo conciso di "puro bash":

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

Questo prende il numero di ripetizioni come primo argomento e la stringa da ripetere (che può essere un singolo carattere, come nella descrizione del problema) come secondo argomento. repecho 7 buscite bbbbbbb(terminate da una nuova riga).

Dennis Williamson ha dato essenzialmente questa soluzione quattro anni fa nella sua eccellente risposta alla creazione di una serie di personaggi ripetuti nello script di shell . Il mio corpo della funzione differisce leggermente dal codice lì:

  • Dato che il focus qui è sulla ripetizione di un singolo personaggio e la shell è bash, è probabilmente sicuro da usare echoinvece di printf. E ho letto la descrizione del problema in questa domanda come espressione di una preferenza con cui stampare echo. La definizione della funzione sopra funziona in bash e ksh93 . Sebbene printfsia più portatile (e di solito dovrebbe essere usato per questo genere di cose), echola sintassi è probabilmente più leggibile.

    I echobuiltin di alcune shell interpretano -da soli come un'opzione - anche se il solito significato di -, usare stdin per l'input, non ha senso echo. zsh fa questo. E sicuramente esistono echos che non riconoscono -n, in quanto non è standard . (Molte shell in stile Bourne non accettano affatto lo stile C per i loop, quindi il loro echocomportamento non deve essere considerato.)

  • Qui il compito è stampare la sequenza; , era per assegnarlo a una variabile.

Se $nè il numero desiderato di ripetizioni e non è necessario riutilizzarlo e si desidera qualcosa di ancora più breve:

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

ndeve essere una variabile - in questo modo non funziona con i parametri posizionali. $sè il testo da ripetere.


2
Evita fortemente di fare versioni in loop. printf "%100s" | tr ' ' '='è ottimale.
ocodo

Buone informazioni di base e complimenti per impacchettare la funzionalità come funzione, che funziona zshanche, per inciso. L'approccio echo-in-a-loop funziona bene per conteggi ripetuti più piccoli, ma per quelli più grandi ci sono alternative conformi a POSIX basate sulle utility , come evidenziato dal commento di @ Slomojo.
mklement0

L'aggiunta di parentesi attorno al ciclo più breve conserva il valore di n senza influire sugli echi:(while ((n--)); do echo -n "$s"; done; echo)

usa printf invece di echo! è molto più portatile (echo -n può funzionare solo su alcuni sistemi). vedi unix.stackexchange.com/questions/65803/… (una delle fantastiche risposte di Stephane Chazelas)
Olivier Dulac,

@OlivierDulac La domanda qui riguarda bash. Indipendentemente dal sistema operativo in uso, se stai usando bash su di esso , bash ha un echobuiltin che supporta -n. Lo spirito di ciò che stai dicendo è assolutamente corretto. printfdovrebbe quasi sempre essere preferito echo, almeno nell'uso non interattivo. Ma non penso che sia stato in alcun modo inappropriato o fuorviante dare una echorisposta a una domanda che lo ha posto e che ha fornito informazioni sufficienti per sapere che avrebbe funzionato . Si noti inoltre che il supporto per ((n--))(senza a $) non è garantito da POSIX.
Eliah Kagan,

4

Python è onnipresente e funziona allo stesso modo ovunque.

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

Carattere e conteggio vengono passati come parametri separati.


Penso che questo fosse l'intento quipython -c "import sys; print(sys.argv[1] * int(sys.argv[2]))" "=" 100
gazhay

@loevborg non è un po 'inverosimile?
Sapphire_Brick

3

In bash 3.0 o versioni successive

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

3

Un altro mezzo per ripetere una stringa arbitraria n volte:

Professionisti:

  • Funziona con shell POSIX.
  • L'output può essere assegnato a una variabile.
  • Ripete qualsiasi stringa.
  • Molto veloce anche con ripetizioni molto grandi.

Contro:

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

Con un terminale ANSI e caratteri US-ASCII da ripetere. È possibile utilizzare una sequenza di escape CSI ANSI. È il modo più veloce per ripetere un personaggio.

#!/usr/bin/env bash

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

O staticamente:

Stampa una riga di 80 volte =:

printf '=\e[80b\n'

limitazioni:

  • Non tutti i terminali comprendono la repeat_charsequenza ANSI CSI.
  • È possibile ripetere solo caratteri ISO ASCII o a byte singolo.
  • Ripeti gli arresti all'ultima colonna, in modo da poter utilizzare un valore elevato per riempire un'intera riga indipendentemente dalla larghezza del terminale.
  • La ripetizione è solo per la visualizzazione. Catturare l'output in una variabile shell non espanderà la repeat_charsequenza ANSI CSI nel carattere ripetuto.

1
Nota minore - REP (CSI b) dovrebbe essere avvolto normalmente se il terminale è in modalità wrapping.
scatto il

3

Ecco cosa uso per stampare una riga di caratteri sullo schermo in Linux (in base alla larghezza del terminale / schermo)

Stampa "=" sullo schermo:

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

Spiegazione:

Stampa un segno di uguale quante volte la sequenza indicata:

printf '=%.0s' #sequence

Usa l'output di un comando (questa è una funzione bash chiamata Sostituzione comandi):

$(example_command)

Dai una sequenza, ho usato da 1 a 20 come esempio. Nel comando finale viene utilizzato il comando tput invece di 20:

seq 1 20

Indica il numero di colonne attualmente utilizzate nel terminale:

tput cols


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

2

Il più semplice è usare questo one-liner in csh / tcsh:

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


2

Un'alternativa più elegante alla soluzione Python proposta potrebbe essere:

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

1

Nel caso in cui desideri ripetere un carattere n volte essendo na VARIABILE numero di volte a seconda, diciamo, della lunghezza di una stringa che puoi fare:

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

Visualizza:

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

lengthnon funzionerà expr, probabilmente intendevi n=$(expr 10 - ${#vari}); tuttavia, è più semplice e più efficiente di utilizzare l'espansione aritmetica di Bash: n=$(( 10 - ${#vari} )). Inoltre, al centro della tua risposta c'è l'approccio molto Perl a cui l'OP sta cercando un'alternativa Bash .
mklement0

1

Questa è la versione più lunga di ciò che Eliah Kagan stava sposando:

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

Ovviamente puoi usare printf anche per quello, ma non per i miei gusti:

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

Questa versione è compatibile Dash:

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

con il numero iniziale.


In bash e con un n positivo: while (( i-- )); do echo -n " "; donefunziona.

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
}

Esecuzioni campione

$ repeatString 'a1' 10 
a1a1a1a1a1a1a1a1a1a1

$ repeatString 'a1' 0 

$ repeatString '' 10 

Lib di riferimento su: https://github.com/gdbtek/linux-cookbooks/blob/master/libraries/util.bash


1

Come potrei farlo con l'eco?

Puoi farlo con echose echoseguito da sed:

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

In realtà, questo echonon è necessario lì.


1

La mia risposta è un po 'più complicata, e probabilmente non perfetta, ma per coloro che cercano di produrre grandi numeri, sono stato in grado di fare circa 10 milioni in 3 secondi.

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

Il più semplice è usare questo one-liner in bash:

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


1

Un'altra opzione è usare GNU seq e rimuovere tutti i numeri e le nuove righe che genera:

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

Questo comando stampa il #personaggio 100 volte.


1

La maggior parte delle soluzioni esistenti dipendono tutte dal {1..10}supporto della sintassi della shell, che è bash- e zsh- specifica, e non funziona in tcshOpenBSD kshe la maggior parte non-bash sh.

Quanto segue dovrebbe funzionare su OS X e su tutti i sistemi * BSD in qualsiasi shell; infatti, può essere utilizzato per generare un'intera matrice di vari tipi di spazio decorativo:

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

Purtroppo, non abbiamo una nuova riga finale; che può essere risolto con un extra printf '\n'dopo il fold:

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

Riferimenti:


Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.