Bash'de bir karakteri nasıl tekrar edebilirim?


240

Bunu nasıl yapabilirim echo?

perl -E 'say "=" x 100'

Ne yazık ki bu Bash değil.
solidsnack

1
yankı ile değil, aynı konuda ruby -e 'puts "=" * 100'ya dapython -c 'print "=" * 100'
Evgeny

1
Harika bir soru. Çok iyi cevaplar. Ben örnek olarak göndeririz ki burada gerçek bir iş yanıtları birini kullandım: github.com/drbeco/oldfiles/blob/master/oldfiles (kullanılıyorsa printfile seq)svrb=`printf '%.sv' $(seq $vrb)`
Dr. Beco

Her şeyi yazdırmak için genel bir çözüm (yeni satırlar da dahil olmak üzere 1 veya daha fazla karakter): Repeat_this () {i = 1; [[$ i "-le" $ 2 "]; printf "% s" "$ 1" yapın; i = $ (($ i + 1)); bitti; printf '\ n';}. Bunun gibi kullanın: Bu "bir şey" sayısını tekrarlayın Örneğin, 3 yeni satır dahil 5 kez yinelenen bir şeyi göstermek için: Repeat_this "$ (printf '\ n \ n \ nthis')" 5. Son printf '\ n' çıkartılabilir (ancak metin dosyaları oluşturmak için koydum ve bunların son karakteri olarak yeni bir satıra ihtiyacı var!)
Olivier Dulac

Yanıtlar:


396

Kullanabilirsiniz:

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

Bu nasıl çalışır:

Bash {1..100} 'i genişletir, böylece komut şöyle olur:

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

Printf'in formatını ayarladım =%.0s, yani =hangi argümanın verildiğine bakılmaksızın her zaman tek bir baskı yapacak . Bu nedenle 100 =s yazdırır .


14
Büyük tekrar sayımlarında bile oldukça iyi performans gösteren harika bir çözüm. Burada ile çağırabilirsiniz sarmalayıcı bir işlev var repl = 100(örneğin, evalbir değişken üzerinde ayracı genişlemesini dayandırılması için hile maalesef gereklidir):repl() { printf "$1"'%.s' $(eval "echo {1.."$(($2))"}"); }
mklement0

7
Bir var kullanarak üst limiti ayarlamak mümkün mü? Denedim ve işe yarayamıyorum.
Mike Purcell

70
Brace genişletmesinde değişkenleri kullanamazsınız. Kullan seqmesela yerine $(seq 1 $limit).
dogbane

11
Bu işlev kazandıran Eğer onu yeniden düzenlemek için en iyisi $s%.0siçin %.0s$saksi tire bir sebep printfhatası.
KomodoDave

5
Bu bana Bash'ın davranışını fark printfetti: hiçbir argüman kalmayıncaya kadar format dizesini uygulamaya devam ediyor. Biçim dizesini yalnızca bir kez işlediğini varsaymıştım!
Jeenu

89

Kolay yolu yok. Ancak, örneğin:

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

Ya da belki standartlara uygun bir yol:

printf %100s |tr " " "="

Bir de var tput rep, ama eldeki terminallerim için (xterm ve linux) desteklemiyorlar :)


3
Seq ile ilk seçeneğin verilen sayıdan bir tane daha az yazdırdığını unutmayın, böylece örnek 99 =karakter basacaktır .
Camilo Martin

13
printf trÇünkü sadece POSIX çözümdür seq, yesve {1..3}POSIX değildir.
Ciro Santilli 法轮功 at 病 六四 事件 法轮功

2
Bir dizeyi tek bir karakterden ziyade tekrarlamak için: printf %100s | sed 's/ /abc/g'- 'abcabcabc ...' çıktıları
John Rix

3
Hiçbir döngü ve yalnızca bir harici komut ( tr) kullanmak için +1 . Ayrıca böyle bir şeye genişletebilirsiniz printf "%${COLUMNS}s\n" | tr " " "=".
musiphil

2
@ mklement0 Son satırdaki hatayı yanlışlıkla saymanızı umuyordum wc. Bundan alabileceğim tek sonuç " seqkullanılmamalı" dır .
Camilo Martin

51

@ Gniourf_gniourf girişi için şapka ucu .

Not: Bu cevap vermez değil orijinal soruya cevap ancak tamamlar tarafından mevcut, faydalı cevaplar performansını karşılaştıran .

Çözümler edilir sadece yürütme hızı açısından karşılaştırıldığında - bellek gereksinimleri vardır değil hesabı (onlar çözümleri arasında farklılık ve büyük tekrarlama sayıları ile fark olabilir) önünde.

Özet:

  • Senin Eğer tekrar sayısıdır küçük , yaklaşık 100, 's kadar söylemek değer ile devam Bash okunur çözümleri başlangıç dış kamu konuların maliyet özellikle Perl en gibi.
    • Pragmatik olarak söylemek gerekirse, tekrarlayan karakterlerin yalnızca bir örneğine ihtiyacınız varsa , mevcut tüm çözümler iyi olabilir.
  • İle büyük tekrarlama sayıları , kullanmak ek programlar onlar çok daha hızlı olacak gibi.
    • Özellikle, Bash'in büyük dizelerle
      (örn. ${var// /=}) Küresel alt dize değiştirme işleminden kaçınılmalıdır , çünkü yasaklanmıştır.

Aşağıdaki zamanlamalar OSX 10.10.4 ve bash 3.2.57 çalıştıran bir 3.2 GHz Intel Core i5 CPU ve Fusion Drive, bir geç-2012 iMac alınan ve 1000 koşular ortalamasıdır.

Girişler:

  • artan yürütme süresine göre listelenir (en hızlısı önce)
  • ön ekiyle birlikte:
    • M... potansiyel olarak çok karakterli bir çözüm
    • S... tek karakterli bir çözüm
    • P ... POSIX uyumlu bir çözüm
  • ardından çözümün kısa bir açıklaması
  • kaynak cevap yazarı adı ile sonek

  • Küçük tekrar sayısı: 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
  • Sadece Bash çözümleri paketi yönetir - ancak bu tekrar küçük sayılır! (aşağıya bakınız).
  • Dış hizmetlerin başlangıç ​​maliyeti, özellikle Perl'ler için önemlidir. Bunu bir döngüde çağırmanız gerekiyorsa - her yinelemede küçük tekrar sayıları ile - çoklu yardımcı programdan awkve perlçözümlerden kaçının .

  • Büyük tekrar sayısı: 1000000 (1 milyon)
[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
  • Sorunun Perl çözümü çok hızlı.
  • Bash'in küresel dize değiştirme ( ${foo// /=}), büyük dizelerle açıklanamaz derecede yavaştır ve Bash 4.3.30'da (50 dakika (!) Ve Bash 3.2.57'de daha da uzun sürdü - hiç beklemedim bitirmek için).
  • Bash döngüleri yavaştır ve aritmetik döngüler ( (( i= 0; ... ))) küme ayracı genişletilmiş olanlardan ( {1..n}) daha yavaştır - aritmetik döngüler daha fazla bellek verimlidir.
  • awkanlamına gelir BSD awk (ayrıca OSX'te bulunduğu gibi) - gözle görülür şekilde daha yavaş olduğunu gawk(GNU AWK) ve özelliklemawk .
  • Büyük sayılar ve çoklu karakter ile unutmayın. bellek tüketimi dikkate alınabilir - yaklaşımlar bu açıdan farklılık gösterir.

İşte Bash komut ( testrepeatyukarıda üretilen). 2 argüman alır:

  • karakter tekrar sayısı
  • isteğe bağlı olarak, ortalama zamanlamayı hesaplamak ve hesaplamak için test çalıştırması sayısı

Başka bir deyişle: yukarıdaki zamanlamalar testrepeat 100 1000vetestrepeat 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

Zamanlama karşılaştırmasını görmek ilginç, ancak birçok programda çıktı tamponlu olduğunu düşünüyorum, bu nedenle tamponlama kapatıldığında zamanlamaları değiştirilebilir.
Sergiy Kolodyazhnyy

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

2
Yani perl çözümü (sid_com) temelde en hızlı ... perl lansmanının ilk yüküne ulaşıldığında. (küçük bir tekrar için 59ms'den bir milyon tekrar için 67ms'ye gider ... bu yüzden perl çatallama sisteminizde yaklaşık 59ms sürdü)
Olivier Dulac

46

Bunu yapmanın birden fazla yolu var.

Bir döngü kullanma:

  • Brace genişletmesi tamsayı değişmez değerleri ile kullanılabilir:

    for i in {1..100}; do echo -n =; done    
  • C benzeri bir döngü değişkenlerin kullanımına izin verir:

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

Yerleşik kullanımı printf:

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

Burada bir hassasiyet belirtilmesi, dizeyi belirtilen genişliğe ( 0) uyacak şekilde keser . Gibi printftekrar kullanım biçimi dize bu basitçe yazdırır tüm argüman tüketmeye "="100 kez.

Kullanılması head( printfvs) ve tr:

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

2
++ , yüksek tekrar sayımlarında bile iyi çalışan head/ trçözüm için (küçük uyarı: head -cPOSIX uyumlu değildir, ancak hem BSD hem de GNU headuygular); bu durumda diğer iki çözüm yavaş olsa da, çok karakterli dizelerle de çalışma avantajına sahiptirler .
mklement0

1
Kullanılması yesve head- kullanışlı sen satırbaşıyla belirli sayıda istiyorsanız: yes "" | head -n 100. trherhangi bir karakteri yazdırabilirsiniz:yes "" | head -n 100 | tr "\n" "="; echo
loxaxs

Biraz şaşırtıcı bir şekilde: versiyondan dd if=/dev/zero count=1 bs=100000000 | tr '\0' '=' >/dev/nullönemli ölçüde yavaş head -c100000000 < /dev/zero | tr '\0' '=' >/dev/null. Tabii ki zaman farkını makul bir şekilde ölçmek için 100M + 'lık bir blok boyutu kullanmanız gerekir. 100M bayt, gösterilen iki sürümle 1,7 sn ve 1 sn sürer. Ben tr çıkardı ve sadece sürüm /dev/nulliçin 0.287 s headve ddbir milyar bayt için sürüm 0.675 s aldım .
Michael Goldshteyn

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

31

Ben seq kullanarak bunu yapmak için çok kolay bir yol buldum:

GÜNCELLEME: Bu seq, OS X ile birlikte gelen BSD'de çalışır.YMMV diğer sürümlerle birlikte

seq  -f "#" -s '' 10

'#' 10 kez yazdıracak, şöyle:

##########
  • -f "#"biçim dizesini sayıları yok saymak ve #her biri için yazdırmak üzere ayarlar .
  • -s '' her sayı arasına eklenen satırları kaldırmak için ayırıcıyı boş bir dizeye ayarlar
  • Sonraki boşluklar -fve -sönemli gibi görünüyor.

EDIT: İşte kullanışlı bir işlevi var ...

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

Buna şöyle diyebilirsiniz ...

repeat "#" 10

NOT: Tekrarlıyorsanız #, tırnak işaretleri önemlidir!


7
Bu bana verir seq: format ‘#’ has no % directive. seqdizeler için değil sayılar içindir. Bkz. Gnu.org/software/coreutils/manual/html_node/seq-invocation.html
John B

Ah, bu yüzden OS X'te bulunan seq'in BSD sürümünü kullanıyordum. Cevabı güncelleyeceğim. Hangi sürümü kullanıyorsunuz?
Sam Salisbury

GNU coreutils'den seq kullanıyorum.
John B

1
@JohnB: BSD seq, burada dizeleri çoğaltmak için akıllıca yeniden konumlandırılıyor: geçirilen biçim dizesi - normalde üretilen sayıları biçimlendirmek için kullanılır - yalnızca çıktının yalnızca bu dizenin kopyalarını içermesi için burada çoğaltılacak dizeyi içerir. Ne yazık ki, GNU biçim dizesinde bir sayı biçiminin varlığında ısrar ediyor , bu da gördüğünüz hata. -fseq
mklement0

1
Güzel yapılmış; ayrıca çok karakterli dizelerle çalışır . Lütfen "$1"(çift tırnak işareti) kullanın , böylece '*'gömülü boşluklu dizeler ve dizeler gibi karakterleri de iletebilirsiniz . Son olarak, kullanabilmek %istiyorsanız, iki katına çıkarmanız gerekir (aksi seqtakdirde bunun bir biçim belirtiminin bir parçası olduğunu düşünürsünüz %f); kullanmak "${1//%/%%}"bununla ilgilenirdi. Kullandığınız (Sözünü gibi) bu yana BSD seq , bu genel olarak BSD benzeri işletim sisteminde çalışacak (örn FreeBSD) - aksine, bu Linux üzerinde çalışma olmaz , GNU seq kullanılır.
mklement0

18

İşte iki ilginç yol:

ubuntu @ ubuntu: ~ $ evet = | kafa -10 | yapıştır -s -d '' -
==========
ubuntu @ ubuntu: ~ $ evet = | kafa -10 | tr -d "\ n"
========== ubuntu @ ubuntu: ~ $ 

Bu ikisinin son derece farklı olduğuna dikkat edin - pasteYöntem yeni bir satırda sona erer. trYöntem değildir.


1
Güzel yapılmış; O not edin BSD paste açıklanamaz gerektiren -d '\0'boş bir sınırlayıcı belirtmek için, ve başarısız olur -d ''- -d '\0'tüm POSIX uyumlu zekâ çalışmalıdır pasteuygulamaları ve aslında çalışır GNU paste de.
mklement0

Ruh olarak benzer, daha az dıştan takma yes | mapfile -n 100 -C 'printf = \#' -c 1
piskopos

@bishop: Komutunuz gerçekten daha az sayıda alt kabuk oluştururken, daha yüksek tekrar sayıları için hala daha yavaş ve daha düşük tekrar sayıları için fark muhtemelen önemli değil; kesin eşik muhtemelen donanıma ve işletim sistemine bağlıdır, örneğin, OSX 10.11.5 makinemde bu cevap zaten 500'de daha hızlıdır; deneyin time yes = | head -500 | paste -s -d '\0' -; time yes | mapfile -n 500 -C 'printf = \#' -c 1. Daha da önemlisi: printfyine de kullanıyorsanız , kabul edilen cevaptan hem daha basit hem de daha verimli yaklaşımı kullanabilirsiniz:printf '%.s=' $(seq 500)
mklement0

13

Basit bir yolu yok. Kullanarak printfve değiştirerek döngülerden kaçının .

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

2
Güzel, ama sadece küçük tekrar sayıları ile makul bir performans sergiliyor. İşte repl = 100, örneğin (bir iz çıkışı vermez) olarak çağrılabilen bir işlev \nrepl() { local ts=$(printf "%${2}s"); printf %s "${ts// /$1}"; }
sarıcısı

1
@ mklement0 Her iki çözümün de işlev sürümlerini sağlamanızdan memnun oldunuz, ikisinde de +1
Camilo Martin

8

Eğer farklı uygulamaları üzerinden POSIX uyumu ve tutarlılığı istiyorsanız echove printfdiğeri sadece daha ve / veya kabukları 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)

... perl -E 'say "=" x 100'hemen hemen her yerde aynı çıktıyı üretecek .


1
Sorun şu ki seqbir POSIX yardımcı programı değil (BSD ve Linux sistemlerinin uygulamaları olmasına rağmen) - while@ Xennex81'in cevabında olduğu gibi bir döngü ile POSIX kabuk aritmetiği yapabilirsiniz ( printf "="doğru olarak önerdiğiniz gibi değil echo -n).
mklement0

1
Hata! Çok haklısın. Bunun gibi şeyler bazen beni geçiyor çünkü standart bir anlam ifade etmiyor. calPOSIX. seqdeğil. Her neyse, cevabı bir while döngüsü ile yeniden yazmak yerine (dediğiniz gibi, zaten diğer cevaplarda) Bir RYO işlevi ekleyeceğim. Bu şekilde daha eğitici ;-).
Geoff Nixon

8

Soru, bunun nasıl yapılacağıydı echo:

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

Bu irade tamamen aynı yapacak perl -E 'say "=" x 100'fakat echosadece.


Şimdi bu alışılmadık bir durumdur, içinde fazladan boşluk boşluklarını karıştırmazsanız veya şunu kullanarak temizleyin: echo -e $ _ {1..100} '\ b =' | col
anthony

1
Kötü bir fikir. Bu başarısız olur $_1, $_2ya da yüz değişkenlerin başka değerleri vardır.
John Kugelman

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

Bu iğrenç . I love it: D
dimo414

6

evalAlt kabukları, harici araçları, küme ayracı genişletmeleri olmayan saf bir Bash yolu (yani, bir değişkente yinelenecek numaraya sahip olabilirsiniz):

Size n(negatif olmayan) bir sayıya genişleyen bir değişken patternve örneğin,

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

Bununla bir işlev yapabilirsiniz:

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

Bu set ile:

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

Bu küçük hile için aşağıdakileri çok kullanıyoruz printf:

  • -v varname: standart çıktıya yazdırmak yerine printf, biçimlendirilmiş dizenin içeriğini değişkene koyacaktır varname.
  • '% * s': printfbağımsız değişkeni karşılık gelen boşluk sayısını yazdırmak için kullanır. Örneğin, printf '%*s' 4242 boşluk basacaktır.
  • Son olarak, değişkenimizde istenen sayıda boşluk olduğunda, tüm boşlukları modelimizle değiştirmek için bir parametre genişletmesi kullanırız: ${var// /$pattern}genişlemesine, genişlemesinin varyerini alan tüm boşluklarla genişler $pattern.

Dolaylı genişletmeyi kullanarak işlevdeki tmpdeğişkenten de kurtulabilirsiniz repeat:

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

Değişken adını iletmek için ilginç varyasyon. Bu çözüm yaklaşık 1.000'e kadar tekrar sayımları için iyi olsa da (ve tahmin edersem muhtemelen çoğu gerçek yaşam uygulaması için iyi), daha yüksek sayımlar için çok yavaş olur (bir sonraki bölüme bakın) yorum Yap).
mklement0

Görünüşe göre bash, parametre genişletme ( ${var//old/new}) bağlamındaki global dize değiştirme işlemleri özellikle yavaş: bash'da dayanılmaz derecede yavaş 3.2.57ve 4.3.30bash'da yavaş , en azından 3.2 Ghz Intel Core i5 makinesindeki OSX 10.10.3 sistemimde: 1.000 sayı, işler yavaş ( 3.2.57) / hızlı ( 4.3.30): 0.1 / 0.004 saniye. Sayımı 10.000'e çıkarmak çarpıcı derecede farklı sayılar sağlar: repeat 10000 = varbash'da yaklaşık 80 saniye (!) Ve bash'da 3.2.57yaklaşık 0.3 saniye 4.3.30(ondan çok daha hızlı 3.2.57, ama yine de yavaş).
mklement0

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

Veya

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

Misal


3
Güzel yapılmış; bu POSIX uyumludur ve yüksek tekrarlı sayımlarda bile oldukça hızlıdır ve aynı zamanda çok karakterli dizeleri de destekler. İşte kabuk versiyonu: awk 'BEGIN { while (c++ < 100) printf "=" }'. (Çağırmak bir parametreli kabuk işlev sarılmış repeat 100 =, örneğin,): repeat() { awk -v count="$1" -v txt=".$2" 'BEGIN { txt=substr(txt, 2); while (i++ < count) printf txt }'; }. (Kukla .öneki karakter ve tamamlayıcı substrBSD bir hata etrafında işe ihtiyaç vardır çağrı awkdeğişken değeri olduğunu geçen başlar ile =araları komutu.)
mklement0

1
NF = 100Çözeltisi (100 almak için olsa çok zeki =, kullanmak gerekir NF = 101). Uyarılar o BSD çöker vardır awk(ama çok hızlı gawkve daha hızlı olan mawk) ve POSIX geçen ne olduğunu atama için NF, ne de alanların kullanılması BEGINblokları. BSD'de awkde hafif bir değişiklikle çalışmasını sağlayabilirsiniz : awk 'BEGIN { OFS = "="; $101=""; print }'(ama merakla, awkdöngü çözümünden daha hızlı olmayan BSD'de). Parametreli bir kabuk çözelti olarak: repeat() { awk -v count="$1" -v txt=".$2" 'BEGIN { OFS=substr(txt, 2); $(count+1)=""; print }'; }.
mklement0

Kullanıcılar için not - NF = 100 numarası eski awk'de segment hatasına neden olur. Bunu original-awkdenemek istiyorsanız, çöktüğü bildirilen BSD'nin awk'ına benzer eski awk'ın Linux altındaki adıdır. Kilitlenmenin genellikle yararlanılabilir bir hata bulma yolundaki ilk adım olduğunu unutmayın. Bu cevap güvensiz kodu teşvik ediyor.

2
Kullanıcılara not - original-awkstandart değildir ve önerilmez
Steven Penny

İlk kod snippet'ine bir alternatif olabilir awk NF=100 OFS='=' <<< ""( bashve kullanarak gawk)
oliv

4

Sorunun asıl amacı bunu sadece kabuğun yerleşik komutlarıyla yapmaktı. Yani fordöngüler ve printfs, meşru olurdu süre rep, perlve ayrıca jotaşağıda olmaz. Yine de, aşağıdaki komut

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

örneğin, bir pencere genişliğinde \/\/\/\/\/\/\/\/\/\/\/\/


2
Güzel yapılmış; bu, yüksek tekrar sayımlarında bile çok işe yarar (çok karakterli dizeleri de desteklerken). Daha iyi bir yaklaşım göstermek için, burada OP'ın komutunun eşdeğer: jot -s '' -b '=' 100. Uyarı, OSX de dahil olmak üzere BSD benzeri platformların gelmesine rağmenjot , Linux dağıtımlarının gelmemesidir .
mklement0

1
Teşekkürler, -s kullanımınızı seviyorum ''. Senaryolarımı değiştirdim.
Stefan Ludwig

Son Debian tabanlı sistemlerde, apt install athena-jotsağlayacaktır jot.
agc

4

Diğerlerinin söylediği gibi, bash ayracı genişletmesinde parametre genişletmesinden önce gelir , bu nedenle aralıklar yalnızca değişmez değerler içerebilir. ve temiz çözümler sunar ancak her birinde aynı kabuğu kullansanız bile bir sistemden diğerine tamamen taşınabilir değildir. (Her ne kadar artan bir şekilde mevcut olsa da; örneğin, FreeBSD 9.3 ve üstü sürümlerde ) ve diğer dolaylı biçimler her zaman işe yarar, ancak bir şekilde yetersizdir.{m,n}seqjotseqeval

Neyse ki, bash döngüler için C stilini destekler (yalnızca aritmetik ifadelerle). İşte özlü bir "saf bash" yolu:

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

Bu, ilk bağımsız değişken olarak tekrar sayısını ve ikinci bağımsız değişken olarak tekrarlanacak dizeyi (sorun açıklamasında olduğu gibi tek bir karakter olabilir) alır. repecho 7 bçıktılar bbbbbbb(bir satırsonu ile sonlandırılır).

Dennis Williamson verdi onun mükemmel cevap dört yıl önce esasen bu çözümü için kabuk komut dosyası tekrarlanan karakter dizesini oluşturma . Benim işlev gövdesi orada kod biraz farklıdır:

  • Buradaki odak tek bir karakteri tekrarlamak ve kabuk bash olduğundan, muhtemelen echoyerine kullanmak güvenlidir printf. Ve bu sorudaki sorun tanımını yazdırmayı tercih ettiğini ifade ediyorum echo. Yukarıdaki fonksiyon tanımı bash ve ksh93 ile çalışır . Her ne kadar printfdaha taşınabilir olsa da (ve genellikle bu tür şeyler için kullanılmalıdır), echosözdizimi muhtemelen daha okunabilirdir.

    Bazı mermilerin echoyapıları -tek başına bir seçenek olarak yorumlanır - her ne kadar -giriş için stdin kullanmak anlamsız olsa da echo. zsh bunu yapar. Ve kesinlikle orada mevcut echotanımaz s -nolarak, standart değildir . (Bourne tarzı mermilerin çoğu döngüler için C-stili kabul etmezler, bu nedenle echodavranışlarının dikkate alınması gerekmez.)

  • Burada görev diziyi yazdırmak; orada bir değişkene atamaktı.

Eğer $ntekrar istenilen sayı ve bunu yeniden gerekmez, ve hatta daha kısa bir şey istiyorum:

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

ndeğişken olmalıdır - bu şekilde konumsal parametrelerle çalışmaz. $stekrarlanacak metindir.


2
Döngü sürümleri yapmaktan kesinlikle kaçının. printf "%100s" | tr ' ' '='en uygunudur.
ocodo

İşlevselliği zshtesadüfen çalışan bir işlev olarak paketlemek için iyi arka plan bilgisi ve kudos . Döngüdeki yankı yaklaşımı daha küçük tekrar sayıları için iyi çalışır, ancak daha büyük olanlar için POSIX uyumlu alternatifler vardır. , @ Slomojo'nun yorumunda kanıtlandığı gibi yardımcı programlara .
mklement0

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

echo yerine printf kullanın! çok daha taşınabilir (echo-n yalnızca bazı sistemlerde çalışabilir). bkz. unix.stackexchange.com/questions/65803/… (Stephane Chazelas'ın müthiş cevabından biri)
Olivier Dulac

@OlivierDulac Buradaki soru bash hakkında. Hangi işletim sistemini çalıştırırsanız kullanın, üzerinde bash kullanıyorsanız , bash'ı echodestekleyen bir yerleşik vardır -n. Söylediklerinizin ruhu kesinlikle doğrudur. en azından interaktif olmayan kullanımda printfneredeyse her zaman tercih edilmelidir echo. Ancak echo, bir soru soran ve işe yarayacağını bilmek için yeterli bilgi veren bir soruya cevap vermenin uygunsuz veya yanıltıcı olduğunu düşünmüyorum . Lütfen ((n--))($ ) kendisinin POSIX tarafından garanti edilmediğini .
Eliah Kagan

4

Python her yerde bulunur ve her yerde aynı şekilde çalışır.

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

Karakter ve sayı ayrı parametreler olarak iletilir.


Sanırım burada niyet vardıpython -c "import sys; print(sys.argv[1] * int(sys.argv[2]))" "=" 100
gazhay

@loevborg o kadar uzak değil mi?
Sapphire_Brick

3

Bash 3.0 veya üstü sürümlerde

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

3

Rasgele bir dizeyi n kez tekrarlamanın başka bir anlamı:

Artıları:

  • POSIX kabuğu ile çalışır.
  • Çıktı bir değişkene atanabilir.
  • Herhangi bir dizeyi tekrarlar.
  • Çok büyük tekrarlarla bile çok hızlı.

Eksileri:

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

Bir ANSI terminali ve US-ASCII karakterleri ile tekrarlayın. Bir ANSI CSI kaçış dizisi kullanabilirsiniz. Bir karakteri tekrar etmenin en hızlı yoludur.

#!/usr/bin/env bash

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

Veya statik olarak:

80 kez bir satır yazdırın =:

printf '=\e[80b\n'

Sınırlamalar:

  • Tüm terminaller repeat_charANSI CSI dizisini anlamaz .
  • Yalnızca US-ASCII veya tek baytlık ISO karakterleri tekrarlanabilir.
  • Son sütunda tekrar durur, böylece terminal genişliğinden bağımsız olarak tüm bir satırı doldurmak için büyük bir değer kullanabilirsiniz.
  • Tekrarlama yalnızca görüntüleme içindir. Çıktıyı bir kabuk değişkenine yakalamak repeat_charANSI CSI dizisini tekrarlanan karaktere genişletmez .

1
Küçük not - Terminal sarma modundaysa REP (CSI b) normal şekilde sarılmalıdır.
jerch

3

Linux'ta ekran boyunca bir karakter satırı yazdırmak için kullandığım şey (terminal / ekran genişliğine göre)

Ekranda "=" yazdırın:

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

Açıklama:

Belirtilen sırayla eşit sayıda işaret yazdırın:

printf '=%.0s' #sequence

Bir komutun çıkışını kullanın (bu, Komut Değiştirme olarak adlandırılan bir bash özelliğidir):

$(example_command)

Bir dizi verin, örnek olarak 1'den 20'ye kadar kullandım. Son komutta 20 yerine tput komutu kullanılır:

seq 1 20

Terminalde şu anda kullanılan sütun sayısını verin:

tput cols


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

2

En basit olanı bu tek astarı csh / tcsh'de kullanmaktır:

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


2

Önerilen Python çözümüne daha zarif bir alternatif olabilir:

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

1

Örneğin, yapabileceğiniz bir dizenin uzunluğuna bağlı olarak bir karakteri n kez DEĞİŞKEN olmak üzere tekrarlamak istediğinizde:

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

Görüntüleniyor:

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

lengthçalışmayacak expr, muhtemelen anlamına geliyordu n=$(expr 10 - ${#vari}); ancak, Bash'in aritmetik kullanımı daha basit ve daha verimli: n=$(( 10 - ${#vari} )). Ayrıca, cevabınızın merkezinde OP'nin Bash alternatifi aradığı Perl yaklaşımı var .
mklement0

1

Eliah Kagan'ın benimsediği şeyin daha uzun versiyonu:

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

Elbette bunun için printf kullanabilirsiniz, ancak benim zevkime göre değil:

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

Bu sürüm Dash uyumludur:

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

ben ilk sayı olmak.


Bash ve pozitif n: ile while (( i-- )); do echo -n " "; doneçalışır.

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
}

Örnek çalışmalar

$ repeatString 'a1' 10 
a1a1a1a1a1a1a1a1a1a1

$ repeatString 'a1' 0 

$ repeatString '' 10 

Referans lib: https://github.com/gdbtek/linux-cookbooks/blob/master/libraries/util.bash


1

Bunu yankı ile nasıl yapabilirim?

Bunu , ardından echogelirse yapabilirsiniz :echosed

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

Aslında, bu echogereksiz.


1

Cevabım biraz daha karmaşık ve muhtemelen mükemmel değil, ancak çok sayıda çıktı almak isteyenler için 3 saniyede yaklaşık 10 milyon yapmayı başardım.

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

En basit olanı, bu tek astarı bash'de kullanmaktır:

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


1

Başka bir seçenek GNU seq kullanmak ve oluşturduğu tüm sayıları ve yeni satırları kaldırmaktır:

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

Bu komut #karakteri 100 kez yazdırır .


1

Mevcut çözümlerin çoğu {1..10}, kabuğun sözdizimi desteğine bağlıdır ; bu bash- ve zsh- spesifiktir ve tcshOpenBSD'lerde kshve çoğu bash içinde çalışmazsh .

Aşağıdakiler OS X ve tüm * BSD sistemleri üzerinde herhangi bir kabukta çalışmalıdır; aslında, çeşitli dekoratif alan türlerinden oluşan bir bütün matris oluşturmak için kullanılabilir:

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

Ne yazık ki, sondaki yeni bir satır almıyoruz; printf '\n'kattan sonra ekstra bir şekilde sabitlenebilir :

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

Referanslar:


Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.