Jak mogę powtórzyć postać w Bash?


240

Jak mogłem to zrobić echo?

perl -E 'say "=" x 100'

Niestety to nie jest Bash.
solidsnack

1
nie echo, ale na ten sam temat ruby -e 'puts "=" * 100'lubpython -c 'print "=" * 100'
Jewgienij,

1
Świetne pytanie. Bardzo dobre odpowiedzi. Użyłem jednej z odpowiedzi w prawdziwej pracy, którą opublikuję jako przykład: github.com/drbeco/oldfiles/blob/master/oldfiles (używane printfz seq)svrb=`printf '%.sv' $(seq $vrb)`
Dr Beco

Ogólne rozwiązanie do drukowania cokolwiek (1 lub więcej znaków, nawet włączając znaki nowej linii): Repeat_this () {i = 1; while [„$ i” -le „2 $”]; do printf "% s" "$ 1"; i = $ (($ i + 1)); Gotowe ; printf '\ n';}. Użyj tego: Powtórz_to „coś” Liczba_repetycji. Na przykład, aby zaprezentować powtarzanie 5 razy, w tym 3 nowe wiersze: Powtórz_to "$ (printf '\ n \ n \ nthis')" 5. Ostatni printf '\ n' może zostać wyjęty (ale wstawiłem go, aby utworzyć pliki tekstowe, a te potrzebują nowego wiersza jako ostatniego znaku!)
Olivier Dulac

Odpowiedzi:


396

Możesz użyć:

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

Jak to działa:

Bash rozwija {1..100}, więc polecenie staje się:

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

Ustawiłem format printf, =%.0sco oznacza, że ​​zawsze będzie drukował jeden, =bez względu na podany argument. Dlatego drukuje 100 =s.


14
Świetne rozwiązanie, które działa dość dobrze nawet przy dużej liczbie powtórzeń. Oto opakowanie funkcji, które można wywołać repl = 100na przykład ( evalniestety wymagane jest repl() { printf "$1"'%.s' $(eval "echo {1.."$(($2))"}"); }
podstęp w

7
Czy można ustawić górny limit za pomocą var? Próbowałem i nie mogę go uruchomić.
Mike Purcell,

70
Nie można używać zmiennych w ramach rozwijania nawiasów. seqZamiast tego użyj np $(seq 1 $limit).
dogbane

11
Jeśli to sfunkcjonalizujesz, najlepiej zmienić jego kolejność, $s%.0saby w %.0s$sprzeciwnym razie kreski spowodowały printfbłąd.
KomodoDave

5
To sprawiło, że zauważyłem zachowanie Basha printf: kontynuuje stosowanie łańcucha formatu, dopóki nie pozostaną żadne argumenty. Zakładałem, że przetworzył ciąg formatu tylko raz!
Jeenu

89

Nie ma łatwego sposobu. Ale na przykład:

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

A może sposób zgodny ze standardami:

printf %100s |tr " " "="

Są też tput rep, ale jeśli chodzi o moje terminale pod ręką (xterm i linux), wydaje się, że nie obsługują tego :)


3
Zauważ, że pierwsza opcja z sekwencją drukuje o jeden mniej niż podana liczba, więc w tym przykładzie wydrukuje 99 =znaków.
Camilo Martin

13
printf trjest jedynym rozwiązaniem, ponieważ POSIX seq, yesa {1..3}nie są POSIX.
Ciro Santilli 10 冠状 病 六四 事件 法轮功

2
Aby powtórzyć ciąg zamiast jednego znaku: printf %100s | sed 's/ /abc/g'- wypisuje „abcabcabc ...”
John Rix,

3
+1 za brak pętli i tylko jedno zewnętrzne polecenie ( tr). Możesz także rozszerzyć go na coś takiego printf "%${COLUMNS}s\n" | tr " " "=".
musiphil

2
@ mklement0 Cóż, miałem nadzieję, że przez pomyłkę liczysz ostatnią nową linię wc. Jedyny wniosek, jaki mogę z tego wyciągnąć, to „ seqnie należy używać”.
Camilo Martin

51

Wskazówka kapelusza do jego @ gniourf_gniourf .

Uwaga: Ta odpowiedź nie odpowiada na pierwotne pytanie, ale uzupełnia istniejące, pomocne odpowiedzi, porównując wydajność .

Rozwiązania są porównywane tylko pod względem szybkości wykonania - wymagania pamięci nie są brane pod uwagę (różnią się w zależności od rozwiązania i mogą mieć znaczenie przy dużej liczbie powtórzeń).

Podsumowanie:

  • Jeśli liczba powtórzeń jest niewielka , powiedzmy do około 100, warto wybrać rozwiązania oparte tylko na Bash , ponieważ koszty uruchomienia zewnętrznych narzędzi są ważne, zwłaszcza Perla.
    • Pragmatycznie rzecz biorąc, jeśli potrzebujesz tylko jednego wystąpienia powtarzających się znaków, wszystkie istniejące rozwiązania mogą być w porządku.
  • Z dużych powtarzanych zarzutów , użyć zewnętrznych narzędzi , jak zostaną one znacznie szybciej.
    • W szczególności unikaj globalnego zastępowania podciągów przez Bash dużymi łańcuchami
      (np. ${var// /=}), Ponieważ jest to zbyt wolne.

Poniżej znajdują się czasy pobrane na komputerze iMac z końca 2012 r. Z procesorem Intel Core i5 3,2 GHz i dyskiem Fusion, działającym w systemie OSX 10.10.4 i bash 3.2.57, i są to średnio 1000 uruchomień.

Wpisy to:

  • wymienione w kolejności rosnącej czasu wykonywania (najpierw najszybszy)
  • z prefiksem:
    • M... potencjalnie rozwiązanie wielu znaków
    • S... rozwiązanie tylko dla jednego znaku
    • P ... rozwiązanie zgodne z POSIX
  • a następnie krótki opis rozwiązania
  • z nazwiskiem autora pierwotnej odpowiedzi

  • Mała liczba powtórzeń: 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
  • Rozwiązania oparte wyłącznie na Bash są na czele - ale tylko z powtarzaniem liczą się tak małe! (patrz poniżej).
  • Tu liczy się koszt uruchomienia zewnętrznych narzędzi, zwłaszcza Perla. Jeśli musisz to wywoływać w pętli - z małą liczbą powtórzeń liczy się w każdej iteracji - unikaj wielofunkcyjności awki perlrozwiązań.

  • Duża liczba powtórzeń: 1000000 (1 milion)
[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
  • Rozwiązanie Perla z pytania jest zdecydowanie najszybsze.
  • Globalna zamiana łańcuchów znaków (Bash ${foo// /=}) jest niewytłumaczalnie powolna z dużymi łańcuchami i została wyłączona z działania (zajęło około 50 minut (!) W wersji Bash 4.3.30, a nawet dłużej w wersji Bash 3.2.57 - nigdy nie czekałem to zakończyć).
  • Pętle Bash są powolne, a pętle arytmetyczne ( (( i= 0; ... ))) są wolniejsze niż pętle z rozszerzeniem nawiasów klamrowych ( {1..n}) - chociaż pętle arytmetyczne są bardziej wydajne pod względem pamięci.
  • awkodnosi się do BSD awk (jak również w OSX) - jest zauważalnie wolniejszy niż gawk(GNU Awk), a zwłaszcza mawk.
  • Zauważ, że przy dużych liczbach i wielu znakach. ciągów, zużycie pamięci może stać się rozważeniem - podejścia różnią się pod tym względem.

Oto skrypt Bash ( testrepeat), który wytworzył powyższe. Wymaga 2 argumentów:

  • liczba powtórzeń znaków
  • opcjonalnie liczba uruchomień testowych do wykonania i obliczenia średniego czasu

Innymi słowy: powyższe czasy uzyskano za pomocą testrepeat 100 1000itestrepeat 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

Interesujące jest porównanie czasowe, ale myślę, że w wielu programach dane wyjściowe są buforowane, więc ich czas można zmienić, jeśli buforowanie zostało wyłączone.
Sergiy Kolodyazhnyy,

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

2
Zatem rozwiązanie perla (sid_com) jest zasadniczo najszybsze ... po osiągnięciu początkowego obciążenia związanego z uruchomieniem perla. (z 59 ms na małe powtórzenie do 67 ms na milion powtórzeń ... więc rozwiązywanie perla zajęło w twoim systemie około 59 ms)
Olivier Dulac

46

Jest na to więcej niż jeden sposób.

Za pomocą pętli:

  • Rozwijania nawiasów klamrowych można używać z literałami całkowitymi:

    for i in {1..100}; do echo -n =; done    
  • Pętla podobna do C umożliwia stosowanie zmiennych:

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

Korzystanie z printfwbudowanego:

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

Określenie tutaj precyzji powoduje obcięcie łańcucha w celu dopasowania do określonej szerokości ( 0). Jak printfponownie wykorzystuje ciąg formatu spożywać wszystkie argumenty, to po prostu drukuje "="100 razy.

Za pomocą head( printfitp.) I tr:

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

2
++ dla head/ trrozwiązania, które działa dobrze nawet przy dużej liczbie powtórzeń (małe zastrzeżenie: head -cnie jest zgodne z POSIX, ale headimplementują go zarówno BSD, jak i GNU ); podczas gdy dwa inne rozwiązania będzie wolno w takim przypadku, mają tę zaletę, że działa z wielu ciągów dwuznakowego też.
mklement0

1
Używanie yesi head- przydatna, jeśli chcesz pewną liczbę nowych linii: yes "" | head -n 100. trmoże sprawić, że wypisze dowolny znak:yes "" | head -n 100 | tr "\n" "="; echo
loxaxs

Nieco zaskakujące: dd if=/dev/zero count=1 bs=100000000 | tr '\0' '=' >/dev/nulljest znacznie wolniejszy niż head -c100000000 < /dev/zero | tr '\0' '=' >/dev/nullwersja. Oczywiście musisz użyć bloku o wielkości 100 M +, aby rozsądnie zmierzyć różnicę czasu. 100M bajtów zajmuje 1,7 si 1 s przy pokazanych dwóch odpowiednich wersjach. Zdjąłem tr i po prostu zrzuciłem go /dev/nulli dostałem 0,287 s dla headwersji i 0,675 s dla ddwersji dla miliarda bajtów.
Michael Goldshteyn,

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

31

Właśnie znalazłem bardzo prosty sposób, aby to zrobić za pomocą seq:

AKTUALIZACJA: Działa na BSD seqdostarczanym z OS X. YMMV z innymi wersjami

seq  -f "#" -s '' 10

Wydrukuje „#” 10 razy, tak jak to:

##########
  • -f "#"ustawia ciąg formatu, aby ignorował liczby i po prostu drukował #dla każdego z nich.
  • -s '' ustawia separator na pusty ciąg, aby usunąć znaki nowej linii, które sekwencja wstawia między każdą liczbą
  • Przestrzenie po -fi -swydają się być ważne.

EDYCJA: Oto przydatna funkcja ...

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

Które możesz tak nazwać ...

repeat "#" 10

UWAGA: Jeśli powtarzasz, #cytaty są ważne!


7
To mi daje seq: format ‘#’ has no % directive. seqjest dla liczb, a nie ciągów. Zobacz gnu.org/software/coreutils/manual/html_node/seq-invocation.html
John B

Ach, więc użyłem wersji BSD seq znalezionej w OS X. Zaktualizuję odpowiedź. Jakiej wersji używasz?
Sam Salisbury,

Używam seq z GNU coreutils.
John B,

1
@JohnB: BSD seqjest sprytnie zmieniane tutaj, aby powielać ciągi : przekazany ciąg formatu -f- zwykle używany do formatowania generowanych liczb - zawiera tylko ciąg do replikacji tutaj, tak że dane wyjściowe zawierają tylko kopie tego ciągu. Niestety, GNU seqnalega na obecność formatu liczbowego w ciągu formatu, który jest błędem, który widzisz.
mklement0

1
Ładnie wykonane; współpracuje również z wielo- -characters strun. Proszę używać "$1"(podwójne cudzysłowy), aby można było także przekazywać znaki takie jak '*'i ciągi znaków z osadzonymi białymi znakami . Wreszcie, jeśli chcesz móc korzystać %, musisz go podwoić (w przeciwnym razie seqpomyślisz, że jest to część specyfikacji formatu, takiej jak %f); używanie "${1//%/%%}"to załatwi. Ponieważ (jak wspomniałeś) używasz BSD seq , będzie to działało ogólnie na systemach operacyjnych podobnych do BSD (np. FreeBSD) - przeciwnie, nie będzie działać na Linuksie , w którym używany jest GNU seq .
mklement0

18

Oto dwa ciekawe sposoby:

ubuntu @ ubuntu: ~ $ yes = | głowa -10 | wklej -s -d '' -
==========
ubuntu @ ubuntu: ~ $ yes = | głowa -10 | tr -d "\ n"
========== ubuntu @ ubuntu: ~ $ 

Zauważ, że te dwa są subtelnie różne - pasteMetoda kończy się na nowej linii. trMetoda nie.


1
Ładnie wykonane; proszę zauważyć, że BSD w paste niewytłumaczalny sposób wymaga -d '\0'określenia pustego separatora i kończy się niepowodzeniem -d ''- -d '\0'powinien działać ze wszystkimi pasteimplementacjami kompatybilnymi z POSIX i rzeczywiście działa również z GNU paste .
mklement0

Podobnie w duchu, z mniejszą liczbą narzędzi zewnętrznych:yes | mapfile -n 100 -C 'printf = \#' -c 1
biskup

@bishop: Chociaż twoje polecenie rzeczywiście tworzy jedną mniej podpowłoki, wciąż jest wolniejsze dla wyższych liczb powtórzeń, a dla mniejszych powtórzeń różnica prawdopodobnie nie ma znaczenia; dokładny próg prawdopodobnie zależy zarówno od sprzętu, jak i systemu operacyjnego, np. na moim komputerze z systemem OSX 10.11.5 ta odpowiedź jest już szybsza przy 500; spróbuj time yes = | head -500 | paste -s -d '\0' -; time yes | mapfile -n 500 -C 'printf = \#' -c 1. Co ważniejsze: jeśli printfmimo to korzystasz , równie dobrze możesz zastosować zarówno prostsze, jak i bardziej wydajne podejście z zaakceptowanej odpowiedzi:printf '%.s=' $(seq 500)
mklement0

13

Nie ma prostego sposobu. Unikaj używania printfi zastępowania pętli .

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

2
Fajnie, ale działa rozsądnie tylko przy małej liczbie powtórzeń. Oto opakowanie funkcji, które można wywołać repl = 100na przykład jako (nie wyświetla końcowego \n):repl() { local ts=$(printf "%${2}s"); printf %s "${ts// /$1}"; }
mklement0

1
@ mklement0 Miło z twojej strony, że możesz dostarczyć funkcjonalne wersje obu rozwiązań, +1 na obu!
Camilo Martin

8

Jeśli chcesz POSIX zgodności i spójności pomiędzy różnymi implementacjami echoi printf, i / lub muszli inne niż 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)

... da taką samą wydajność jak perl -E 'say "=" x 100'prawie wszędzie.


1
Problem polega na tym, że seqnie jest to narzędzie POSIX (chociaż systemy BSD i Linux mają takie implementacje) - whilezamiast tego można wykonywać arytmetykę powłoki POSIX za pomocą pętli, jak w odpowiedzi @ Xennex81 (zamiast, jak printf "="słusznie sugerujesz, zamiast echo -n).
mklement0

1
Ups, masz całkowitą rację. Takie rzeczy czasem mijały mnie, ponieważ ten standard nie ma sensu. caljest POSIX. seqnie jest. W każdym razie, zamiast przepisać odpowiedź za pomocą pętli while (jak mówisz, to już jest w innych odpowiedziach) dodam funkcję RYO. Więcej edukacyjnych w ten sposób ;-).
Geoff Nixon

8

Pytanie dotyczyło tego, jak to zrobić echo:

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

To zrobi dokładnie to samo, perl -E 'say "=" x 100'ale echotylko z .


Jest to niezwykłe, jeśli nie złączysz w nim dodatkowych spacji .. lub wyczyścisz go za pomocą: echo -e $ _ {1..100} '\ b =' | col
Anthony

1
Kiepski pomysł. To się nie powiedzie, jeśli $_1, $_2lub jakakolwiek inna ze stu zmiennych ma wartości.
John Kugelman,

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

To jest obrzydliwe . Uwielbiam to: D
dimo414

6

Czysty sposób Bash bez eval, bez podpowłoki, bez zewnętrznych narzędzi, bez rozszerzeń nawiasów (tzn. Możesz mieć liczbę do powtórzenia w zmiennej):

Jeśli otrzymasz zmienną, nktóra rozwija się do liczby (nieujemnej) i zmienną pattern, np.

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

Możesz wykonać funkcję za pomocą tego:

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

Z tym zestawem:

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

Do tej małej sztuczki używamy printfcałkiem sporo z:

  • -v varname: zamiast drukowania na standardowe wyjście, printfumieści zawartość sformatowanego ciągu w zmiennej varname.
  • „% * s”: printfużyje argumentu do wydrukowania odpowiedniej liczby spacji. Np. printf '%*s' 42Wydrukuje 42 spacje.
  • Na koniec, gdy mamy pożądaną liczbę spacji w naszej zmiennej, używamy rozszerzenia parametru, aby zastąpić wszystkie spacje naszym wzorcem: ${var// /$pattern}rozwinie się do rozwinięcia varz wszystkimi spacjami zastąpionymi przez rozwinięcie $pattern.

Możesz także pozbyć się tmpzmiennej w repeatfunkcji, używając rozszerzenia pośredniego:

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

Interesująca odmiana do przekazania nazwy zmiennej. Chociaż to rozwiązanie jest dobre dla powtarzania zliczeń do około 1000 (a więc prawdopodobnie jest dobre dla większości rzeczywistych aplikacji, jeśli bym zgadywał), robi się bardzo wolno dla wyższych zliczeń (patrz dalej komentarz).
mklement0

Wydaje się, że bashglobalne operacje zamiany ciągów w kontekście interpretacji parametrów ( ${var//old/new}) są szczególnie powolne: niesamowicie powolne w bashu 3.2.57i powolne w bashu 4.3.30, przynajmniej w moim systemie OSX 10.10.3 na maszynie Intel Core i5 3,2 Ghz: Z liczba 1000, rzeczy są wolne ( 3.2.57) / szybkie ( 4.3.30): 0,1 / 0,004 sekundy. Zwiększenie liczby do 10.000 daje uderzająco różne liczby: repeat 10000 = vartrwa około 80 sekund (!) W uderzeniu 3.2.57i około 0,3 sekundy w uderzeniu 4.3.30(znacznie szybciej niż w trybie włączonym 3.2.57, ale wciąż powolnym).
mklement0

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

Lub

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

Przykład


3
Ładnie wykonane; jest to zgodne z POSIX i dość szybkie, nawet przy dużej liczbie powtórzeń, jednocześnie obsługując ciągi wieloznakowe. Oto wersja shell: awk 'BEGIN { while (c++ < 100) printf "=" }'. Owinięty w sparametryzowanej funkcję powłoki (Wywołanie jak repeat 100 =, na przykład) repeat() { awk -v count="$1" -v txt=".$2" 'BEGIN { txt=substr(txt, 2); while (i++ < count) printf txt }'; }. (Atrapa .przedrostka char i komplementarne substrwywołanie są potrzebne do obejścia błędu w BSD awk, gdzie przekazanie wartości zmiennej rozpoczynającej się od =złamania polecenia.)
mklement0

1
NF = 100Rozwiązanie jest bardzo mądry (chociaż dostać 100 =, należy użyć NF = 101). Te zastrzeżenia są takie, że wywala BSD awk(ale to bardzo szybko z gawk, a nawet szybciej mawk), a który omawia POSIX ani przypisywanie do NF, ani wykorzystanie pól w BEGINblokach. Możesz sprawić, że będzie działał również w BSD awkz niewielką poprawką: awk 'BEGIN { OFS = "="; $101=""; print }'(ale co ciekawe, w BSD, awkktóra nie jest szybsza niż rozwiązanie pętli). Jako sparametryzowanego roztworu powłoki: repeat() { awk -v count="$1" -v txt=".$2" 'BEGIN { OFS=substr(txt, 2); $(count+1)=""; print }'; }.
mklement0

Uwaga dla użytkowników - trik NF = 100 powoduje błąd segmentu w starszym awk. Jest original-awkto nazwa pod Linuksem starszego awk podobnego do awk BSD, który również został zgłoszony do awarii, jeśli chcesz tego spróbować. Pamiętaj, że zawieszanie się jest zwykle pierwszym krokiem do znalezienia możliwego do wykorzystania błędu. Ta odpowiedź tak promuje niepewny kod.

2
Uwaga dla użytkowników - original-awkjest niestandardowa i nie jest zalecana
Steven Penny

Alternatywą dla pierwszego fragmentu kodu może być awk NF=100 OFS='=' <<< ""(użycie bashi gawk)
oliv

4

Myślę, że pierwotnym celem tego pytania było zrobienie tego tylko za pomocą wbudowanych poleceń powłoki. Więc forpętle i printfs byłby uzasadniony, natomiast rep, perli również jotponiżej nie. Mimo to następujące polecenie

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

na przykład drukuje całą linię okna \/\/\/\/\/\/\/\/\/\/\/\/


2
Ładnie wykonane; działa to dobrze nawet przy dużej liczbie powtórzeń (jednocześnie obsługując ciągi wieloznakowe). Aby lepiej zilustrować podejście, tutaj jest równoznaczne z poleceniem PO za: jot -s '' -b '=' 100. Zastrzeżenie jest to, że podczas BSD-like platform, w tym OSX, wyposażone jot, dystrybucje Linuksa nie .
mklement0

1
Dzięki, podoba mi się twoje użycie -s '' jeszcze bardziej. Zmieniłem swoje skrypty.
Stefan Ludwig

W najnowszych systemach opartych na Debianie , apt install athena-jotzapewniłoby to jot.
agc

4

Jak powiedzieli inni, w interpretacji nawiasów klamrowych poprzedza się interpretację parametrów , więc zakresy mogą zawierać tylko literały. i zapewniają czyste rozwiązania, ale nie są w pełni przenośne z jednego systemu do drugiego, nawet jeśli używasz tej samej powłoki na każdym z nich. (Chociaż jest coraz bardziej dostępny; np. We FreeBSD 9.3 i nowszych .) I inne formy pośrednictwa zawsze działają, ale są nieco nieeleganckie.{m,n}seqjotseqeval

Na szczęście bash obsługuje styl C dla pętli (tylko z wyrażeniami arytmetycznymi). Oto więc zwięzły „czysty bash”:

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

Pobiera to liczbę powtórzeń jako pierwszy argument, a ciąg do powtórzenia (który może być pojedynczym znakiem, jak w opisie problemu) jako drugi argument. repecho 7 bwyjścia bbbbbbb(zakończone nową linią).

Dennis Williamson podał zasadniczo to rozwiązanie cztery lata temu w swojej doskonałej odpowiedzi na temat Tworzenie ciągu powtarzających się znaków w skrypcie powłoki . Moje ciało funkcji różni się nieznacznie od kodu tam:

  • Ponieważ nacisk kładziony jest tutaj na powtarzanie jednego znaku, a powłoka jest bash, prawdopodobnie można go bezpiecznie używać echozamiast printf. Przeczytałem opis problemu w tym pytaniu jako wyrażenie preferencji drukowania echo. Powyższa definicja funkcji działa w bash i ksh93 . Chociaż printfjest bardziej przenośny (i zwykle powinien być używany do tego typu rzeczy), echojego składnia jest prawdopodobnie bardziej czytelna.

    Niektóre echowbudowane powłoki interpretują -się jako opcja - chociaż zwykłe znaczenie -używania stdin jako danych wejściowych jest bezsensowne echo. zsh to robi. I zdecydowanie istnieją takie echo, które nie rozpoznają -n, ponieważ nie jest to standard . (Wiele powłok w stylu Bourne'a w ogóle nie akceptuje pętli w stylu C, dlatego ich echozachowanie nie musi być brane pod uwagę ...)

  • Tutaj zadaniem jest wydrukowanie sekwencji; tam miał przypisać ją do zmiennej.

Jeśli $njest pożądana liczba powtórzeń i nie musisz jej ponownie używać, a chcesz czegoś jeszcze krótszego:

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

nmusi być zmienną - w ten sposób nie działa z parametrami pozycyjnymi. $sto tekst do powtórzenia.


2
Zdecydowanie unikaj wykonywania pętli. printf "%100s" | tr ' ' '='jest optymalny.
ocodo

Dobre informacje w tle i podziękowania za pakowanie funkcjonalności jako funkcji, która działa zshrównież przy okazji. Podejście echo w pętli działa dobrze dla mniejszych zliczeń powtórzeń, ale dla większych istnieją alternatywy zgodne z POSIX oparte na narzędziach , o czym świadczy komentarz @ Slomojo.
mklement0

Dodanie nawiasów wokół krótszej pętli zachowuje wartość n bez wpływu na (while ((n--)); do echo -n "$s"; done; echo)

użyj printf zamiast echa! jest znacznie bardziej przenośny (echo -n może działać tylko w niektórych systemach). zobacz unix.stackexchange.com/questions/65803/… (jedna z niesamowitych odpowiedzi Stephane Chazelas)
Olivier Dulac

@OlivierDulac Pytanie dotyczy bash. Bez względu na używany system operacyjny, jeśli używasz na nim bash, bash ma echowbudowaną obsługę -n. Duch tego, co mówisz, jest absolutnie poprawny. printfprawie zawsze powinno być preferowaneecho , przynajmniej w zastosowaniach nieinteraktywnych. Ale nie sądzę, aby udzielanie echoodpowiedzi na pytanie, które zadawało pytanie, było w jakikolwiek sposób niewłaściwe lub mylące i dawało wystarczającą ilość informacji, aby wiedzieć, że zadziała . Należy również pamiętać, że POSIX nie gwarantuje wsparcia dla ((n--))(bez a $).
Eliah Kagan

4

Python jest wszechobecny i wszędzie działa tak samo.

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

Znak i liczba są przekazywane jako osobne parametry.


Myślę, że taki był celpython -c "import sys; print(sys.argv[1] * int(sys.argv[2]))" "=" 100
gazhay

@loevborg nie jest aż tak daleko posunięte?
Sapphire_Brick


3

Kolejny sposób na powtórzenie dowolnego ciągu n razy:

Plusy:

  • Działa z powłoką POSIX.
  • Dane wyjściowe można przypisać do zmiennej.
  • Powtarza dowolny ciąg.
  • Bardzo szybko, nawet przy bardzo dużych powtórzeniach.

Cons:

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

Z terminalem ANSI i znakami US-ASCII do powtórzenia. Możesz użyć sekwencji ucieczki ANSI CSI. Jest to najszybszy sposób na powtórzenie postaci.

#!/usr/bin/env bash

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

Lub statycznie:

Wydrukuj linię 80 razy =:

printf '=\e[80b\n'

Ograniczenia:

  • Nie wszystkie terminale rozumieją repeat_charsekwencję ANSI CSI.
  • Można powtarzać tylko znaki US-ASCII lub jednobajtowe znaki ISO.
  • Powtórz zatrzymania w ostatniej kolumnie, dzięki czemu możesz użyć dużej wartości do wypełnienia całej linii niezależnie od szerokości terminala.
  • Powtórzenie jest tylko do wyświetlenia. Przechwytywanie danych wyjściowych do zmiennej powłoki nie rozszerzy repeat_charsekwencji ANSI CSI do powtarzanego znaku.

1
Drobna uwaga - REP (CSI b) powinien owijać się normalnie, jeśli terminal jest w trybie owijania.
jerch

3

Oto, czego używam do drukowania linii znaków na ekranie w systemie Linux (na podstawie szerokości terminala / ekranu)

Drukuj „=” na ekranie:

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

Wyjaśnienie:

Wydrukuj znak równości tyle razy, ile podana sekwencja:

printf '=%.0s' #sequence

Użyj danych wyjściowych polecenia (jest to funkcja bash o nazwie Command Substitution):

$(example_command)

Podaj sekwencję, użyłem od 1 do 20 jako przykład. W ostatnim poleceniu użyto polecenia tput zamiast 20:

seq 1 20

Podaj liczbę kolumn aktualnie używanych w terminalu:

tput cols


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

2

Najprościej jest użyć tego jednowierszowego w csh / tcsh:

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


2

Bardziej elegancką alternatywą dla proponowanego rozwiązania Python może być:

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

1

Jeśli chcesz powtórzyć znak n razy NA ZMIENNĄ liczbę razy w zależności, powiedzmy, od długości łańcucha, który możesz wykonać:

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

To pokazuje:

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

lengthnie będzie działać expr, prawdopodobnie miałeś na myśli n=$(expr 10 - ${#vari}); Jednakże, jest to prostsze i bardziej efektywne w użyciu arytmetyczną ekspansję atakujących za: n=$(( 10 - ${#vari} )). Również u podstaw twojej odpowiedzi znajduje się podejście Perla, w którym OP szuka alternatywy dla Bash .
mklement0

1

To jest dłuższa wersja tego, co opowiadał Eliah Kagan:

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

Oczywiście możesz również użyć printf, ale nie do końca tak:

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

Ta wersja jest kompatybilna z Dash:

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

gdzie I jest liczbą początkową.


W bashu i z dodatnim n: while (( i-- )); do echo -n " "; donedziała.


1

Jak mogę to zrobić za pomocą echa?

Możesz to zrobić, echojeśli echopo nim następuje sed:

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

W rzeczywistości echojest to niepotrzebne.


1

Moja odpowiedź jest nieco bardziej skomplikowana i prawdopodobnie nie idealna, ale dla tych, którzy szukają dużych liczb, byłem w stanie zrobić około 10 milionów w 3 sekundy.

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

Najprostszym jest użycie tej jednowarstwowej w bash:

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


1

Inną opcją jest użycie GNU seq i usunięcie wszystkich liczb i znaków nowej linii, które generuje:

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

To polecenie drukuje #znak 100 razy.


1

Większość istniejących rozwiązań zależy od {1..10}obsługi składni powłoki, która jest bash- i zsh- specyficzna i nie działa w tcshlub OpenBSD kshi większość nie bashsh .

Następujące powinny działać na OS X i wszystkich systemach * BSD w dowolnej powłoce; w rzeczywistości można go wykorzystać do wygenerowania całej matrycy różnego rodzaju przestrzeni dekoracyjnej:

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

Niestety nie otrzymujemy końca nowej linii; które można naprawić za pomocą dodatkowego printf '\n'po złożeniu:

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

Bibliografia:


Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.