Bagaimana saya bisa mengulangi karakter dalam Bash?

240

Bagaimana saya bisa melakukan ini echo?

perl -E 'say "=" x 100'
sid_com
sumber
Sayangnya ini bukan Bash.
solidsnack
1
tidak dengan gema, tetapi pada topik yang sama ruby -e 'puts "=" * 100'ataupython -c 'print "=" * 100'
Evgeny
1
Pertanyaan yang bagus Jawaban yang sangat bagus Saya telah menggunakan salah satu jawaban dalam pekerjaan nyata di sini, yang akan saya posting sebagai contoh: github.com/drbeco/oldfiles/blob/master/oldfiles (digunakan printfbersama seq)svrb=`printf '%.sv' $(seq $vrb)`
Dr Beco
Solusi umum untuk mencetak apa saja (1 atau lebih karakter, bahkan termasuk baris baru): Repeat_this () {i = 1; sementara ["$ i" -le "$ 2"]; lakukan printf "% s" "$ 1"; i = $ (($ i +1)); dilakukan; printf '\ n';}. Gunakan seperti ini: Repeat_ini "sesuatu" Number_of_repetitions. Misalnya, untuk menampilkan 5 kali sesuatu yang berulang termasuk 3 baris baru: Repeat_ini "$ (printf '\ n \ n \ nini')" 5. Hasil cetak akhir '\ n' dapat dihapus (tapi saya masukkan untuk membuat file teks, dan mereka membutuhkan baris baru sebagai karakter terakhir mereka!)
Olivier Dulac

Jawaban:

396

Kamu bisa memakai:

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

Bagaimana ini bekerja:

Bash mengembang {1..100} sehingga perintahnya menjadi:

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

Saya telah mengatur format printf =%.0syang artinya akan selalu mencetak satu pun, =tidak peduli apa argumennya. Karena itu mencetak 100 =s.

dogbane
sumber
14
Solusi hebat yang berkinerja cukup baik bahkan dengan jumlah pengulangan yang besar. Inilah pembungkus fungsi yang dapat Anda gunakan repl = 100, misalnya ( evaldiperlukan tipu daya, untuk mendasarkan ekspansi penjepit pada variabel):repl() { printf "$1"'%.s' $(eval "echo {1.."$(($2))"}"); }
mklement0
7
Apakah mungkin untuk menetapkan batas atas menggunakan var? Saya sudah mencoba dan tidak berhasil.
Mike Purcell
70
Anda tidak dapat menggunakan variabel dalam ekspansi brace. Gunakan, seqmisalnya $(seq 1 $limit).
dogbane
11
Jika Anda memfungsikan ini, lebih baik atur ulang dari $s%.0sagar %.0s$standa hubung menyebabkan printfkesalahan.
KomodoDave
5
Ini membuat saya memperhatikan perilaku Bash printf: terus menerapkan format string hingga tidak ada argumen yang tersisa. Saya berasumsi itu memproses string format hanya sekali!
Jeenu
89

Tidak mudah. Tapi misalnya:

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

Atau mungkin cara yang sesuai standar:

printf %100s |tr " " "="

Ada juga a tput rep, tetapi untuk terminal saya di tangan (xterm dan linux) mereka tampaknya tidak mendukungnya :)


sumber
3
Perhatikan bahwa opsi pertama dengan seq mencetak satu kurang dari angka yang diberikan, sehingga contoh itu akan mencetak 99 =karakter.
Camilo Martin
13
printf tradalah satu-satunya solusi POSIX karena seq, yesdan {1..3}bukan POSIX.
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
2
Untuk mengulang string daripada hanya satu karakter: printf %100s | sed 's/ /abc/g'- output 'abcabcabc ...'
John Rix
3
+1 untuk tidak menggunakan loop dan hanya satu perintah eksternal ( tr). Anda juga dapat memperluasnya ke sesuatu seperti printf "%${COLUMNS}s\n" | tr " " "=".
musiphil
2
@ mklement0 Ya, saya berharap Anda menghitung baris baru terakhir karena kesalahan wc. Satu-satunya kesimpulan yang bisa saya ambil dari ini adalah " seqtidak boleh digunakan".
Camilo Martin
51

Ujung topi untuk @ gniourf_gniourf untuk masukannya.

Catatan: Jawaban ini tidak menjawab pertanyaan awal, tetapi melengkapi jawaban yang ada dan bermanfaat dengan membandingkan kinerja .

Solusi dibandingkan dalam hal kecepatan eksekusi saja - persyaratan memori tidak diperhitungkan (mereka berbeda di seluruh solusi dan mungkin penting dengan jumlah pengulangan yang besar).

Ringkasan:

  • Jika jumlah pengulangan Anda kecil , katakan hingga sekitar 100, ada baiknya menggunakan solusi Bash-only , karena biaya startup dari utilitas eksternal penting, terutama Perl.
    • Namun, secara pragmatis, jika Anda hanya membutuhkan satu contoh karakter yang berulang, semua solusi yang ada mungkin baik-baik saja.
  • Dengan jumlah pengulangan yang besar , gunakan utilitas eksternal , karena akan jauh lebih cepat.
    • Secara khusus, hindari penggantian substring global Bash dengan string besar
      (misalnya, ${var// /=}), karena sangat lambat.

Berikut ini adalah waktu yang diambil pada iMac akhir 2012 dengan CPU Intel Core i5 3,2 GHz dan Fusion Drive, menjalankan OSX 10.10.4 dan bash 3.2.57, dan merupakan rata-rata 1000 berjalan.

Entri adalah:

  • terdaftar dalam urutan durasi eksekusi (tercepat pertama)
  • diawali dengan:
    • M... solusi multi-karakter yang berpotensi
    • S... solusi tunggal -karakter-saja
    • P ... solusi yang sesuai dengan POSIX
  • diikuti oleh deskripsi singkat dari solusinya
  • diakhiri dengan nama penulis dari jawaban yang berasal

  • Jumlah pengulangan kecil: 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
  • Solusi Bash-only memimpin paket - tetapi hanya dengan hitungan ulang sekecil ini! (Lihat di bawah).
  • Biaya permulaan utilitas eksternal memang penting di sini, terutama Perl. Jika Anda harus menyebutnya dalam satu lingkaran - dengan jumlah pengulangan kecil di setiap iterasi - hindari multi-utilitas awk,, dan perlsolusi.

  • Hitungan ulang yang besar: 1000000 (1 juta)
[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
  • Solusi Perl dari pertanyaan sejauh ini adalah yang tercepat.
  • Penggantian string global Bash ( ${foo// /=}) jelas-jelas sangat lambat dengan string besar, dan telah dikeluarkan dari menjalankan (butuh sekitar 50 menit (!) Di Bash 4.3.30, dan bahkan lebih lama di Bash 3.2.57 - Saya tidak pernah menunggu untuk sampai selesai).
  • Bash loop lambat, dan loop aritmatika ( (( i= 0; ... ))) lebih lambat daripada yang diperluas brace ( {1..n}) - meskipun loop aritmatika lebih hemat memori.
  • awkmengacu pada BSD awk (seperti juga ditemukan pada OSX) - ini terasa lebih lambat daripada gawk(GNU Awk) dan terutama mawk.
  • Perhatikan bahwa dengan jumlah besar dan multi-char. string, konsumsi memori dapat menjadi pertimbangan - pendekatan berbeda dalam hal itu.

Berikut skrip Bash ( testrepeat) yang menghasilkan hal di atas. Dibutuhkan 2 argumen:

  • jumlah pengulangan karakter
  • opsional, jumlah tes berjalan untuk melakukan dan menghitung waktu rata - rata dari

Dengan kata lain: timing di atas diperoleh dengan testrepeat 100 1000dantestrepeat 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
mklement0
sumber
Sangat menarik untuk melihat perbandingan waktu, tetapi saya pikir dalam banyak program output buffer, jadi waktunya dapat diubah jika buffering dimatikan.
Sergiy Kolodyazhnyy
In order to use brace expansion with a variable, we must use `eval`👍
pyb
2
Jadi solusi perl (sid_com) pada dasarnya adalah yang tercepat ... setelah overhead awal peluncuran perl tercapai. (mulai dari 59ms untuk pengulangan kecil hingga 67ms untuk satu juta pengulangan ... jadi forking perl memerlukan sekitar 59ms pada sistem Anda)
Olivier Dulac
46

Ada lebih dari satu cara untuk melakukannya.

Menggunakan loop:

  • Ekspansi Brace dapat digunakan dengan literal integer:

    for i in {1..100}; do echo -n =; done    
  • Lingkaran mirip-C memungkinkan penggunaan variabel:

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

Menggunakan printfbuiltin:

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

Menentukan presisi di sini memotong string agar sesuai dengan lebar yang ditentukan ( 0). Saat printfmenggunakan kembali format string untuk menggunakan semua argumen, ini hanya mencetak "="100 kali.

Menggunakan head( printf, dll) dan tr:

head -c 100 < /dev/zero | tr '\0' '='
printf %100s | tr " " "="
Eugene Yarmash
sumber
2
++ untuk head/ trsolusi, yang bekerja dengan baik bahkan dengan jumlah pengulangan yang tinggi (peringatan kecil: head -ctidak kompatibel dengan POSIX, tetapi BSD dan GNU headmengimplementasikannya); sementara dua solusi lainnya akan lambat dalam hal itu, mereka memiliki keuntungan bekerja dengan string multi- karakter juga.
mklement0
1
Menggunakan yesdan head- berguna jika Anda ingin sejumlah baris: yes "" | head -n 100. trdapat membuatnya mencetak karakter apa pun:yes "" | head -n 100 | tr "\n" "="; echo
loxaxs
Agak mengherankan: dd if=/dev/zero count=1 bs=100000000 | tr '\0' '=' >/dev/nullsecara signifikan lebih lambat daripada head -c100000000 < /dev/zero | tr '\0' '=' >/dev/nullversi. Tentu saja Anda harus menggunakan ukuran blok 100M + untuk mengukur perbedaan waktu secara wajar. 100M byte membutuhkan 1,7 s dan 1 s dengan dua versi masing-masing ditampilkan. Saya melepas tr dan hanya membuangnya ke /dev/nulldan mendapatkan 0,287 untuk headversi dan 0,675 untuk ddversi untuk satu miliar byte.
Michael Goldshteyn
Untuk: dd if=/dev/zero count=1 bs=100000000 | tr '\0' '=' >/dev/null=> 0,21332 s, 469 MB/s; Untuk: dd if=/dev/zero count=100 bs=1000000| tr '\0' '=' >/dev/null=> 0,161579 s, 619 MB/s;
3ED
31

Saya baru saja menemukan cara serius yang sangat mudah untuk melakukan ini menggunakan seq:

UPDATE: Ini berfungsi pada BSD seqyang datang dengan OS X. YMMV dengan versi lain

seq  -f "#" -s '' 10

Akan mencetak '#' 10 kali, seperti ini:

##########
  • -f "#"mengatur string format untuk mengabaikan angka dan hanya mencetak #untuk masing-masing.
  • -s '' set pemisah ke string kosong untuk menghapus baris baru yang memasukkan seq antara setiap nomor
  • Ruang setelah -fdan -stampaknya menjadi penting.

EDIT: Ini dia dalam fungsi praktis ...

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

Yang bisa Anda panggil seperti ini ...

repeat "#" 10

CATATAN: Jika Anda mengulangi #maka kutipan itu penting!

Sam Salisbury
sumber
7
Ini memberiku seq: format ‘#’ has no % directive. seqadalah untuk angka, bukan string. Lihat gnu.org/software/coreutils/manual/html_node/seq-invocation.html
John B
Ah, jadi saya menggunakan versi BSD dari seq yang ditemukan di OS X. Saya akan memperbarui jawabannya. Versi mana yang Anda gunakan?
Sam Salisbury
Saya menggunakan seq dari GNU coreutils.
John B
1
@JohnB: BSD seqsedang diputar ulang dengan cerdik di sini untuk mereplikasi string : string format dilewatkan ke -f- biasanya digunakan untuk memformat angka yang dihasilkan - hanya berisi string untuk direplikasi di sini sehingga output berisi salinan string itu saja. Sayangnya, GNU seqmenegaskan keberadaan format angka dalam string format, yang merupakan kesalahan yang Anda lihat.
mklement0
1
Bagus sekali; juga bekerja dengan string multi- karakter. Silakan gunakan "$1"(tanda kutip ganda), sehingga Anda juga dapat memasukkan karakter seperti '*'dan string dengan spasi kosong. Akhirnya, jika Anda ingin dapat menggunakan %, Anda harus menggandakannya (jika tidak seqakan berpikir itu bagian dari spesifikasi format seperti %f); menggunakan "${1//%/%%}"akan mengurus hal itu. Karena (seperti yang Anda sebutkan) Anda menggunakan BSD seq , ini akan bekerja pada OS mirip BSD secara umum (misalnya, FreeBSD) - sebaliknya, itu tidak akan bekerja di Linux , di mana GNU seq digunakan.
mklement0
18

Inilah dua cara menarik:

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

Perhatikan bahwa keduanya agak berbeda - pasteMetode ini berakhir pada baris baru. The trMetode tidak.

Trauma Digital
sumber
1
Bagus sekali; harap dicatat bahwa BSD secara paste tak terduga mengharuskan -d '\0'untuk menentukan pembatas kosong, dan gagal dengan -d ''- -d '\0'harus bekerja dengan semua pasteimplementasi yang kompatibel dengan POSIX dan memang bekerja dengan GNU paste juga.
mklement0
Mirip roh, dengan lebih sedikit alat tempel:yes | mapfile -n 100 -C 'printf = \#' -c 1
uskup
@ bishop: Walaupun perintah Anda memang membuat satu subkulit lebih sedikit, masih lebih lambat untuk jumlah pengulangan yang lebih tinggi, dan untuk jumlah pengulangan yang lebih rendah perbedaannya mungkin tidak masalah; ambang yang tepat mungkin bergantung pada perangkat keras dan OS, misalnya, pada mesin OSX 10.11.5 saya, jawaban ini sudah lebih cepat pada angka 500; coba time yes = | head -500 | paste -s -d '\0' -; time yes | mapfile -n 500 -C 'printf = \#' -c 1. Namun yang lebih penting: jika Anda printftetap menggunakan , Anda sebaiknya menggunakan pendekatan yang lebih sederhana dan lebih efisien dari jawaban yang diterima:printf '%.s=' $(seq 500)
mklement0
13

Tidak ada cara sederhana. Hindari penggunaan printfdan penggantian loop .

str=$(printf "%40s")
echo ${str// /rep}
# echoes "rep" 40 times.
Tim
sumber
2
Bagus, tetapi hanya berkinerja cukup dengan jumlah pengulangan yang kecil. Inilah pembungkus fungsi yang dapat dipanggil sebagai repl = 100, misalnya (tidak menampilkan trailing \n):repl() { local ts=$(printf "%${2}s"); printf %s "${ts// /$1}"; }
mklement0
1
@ mklement0 Senang Anda dapat menyediakan versi fungsi dari kedua solusi, memberi +1 pada keduanya!
Camilo Martin
8

Jika Anda ingin kepatuhan dan konsistensi POSIX di berbagai implementasi berbeda dari echodan printf, dan / atau shell selain dari hanya 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)

... akan menghasilkan output yang sama seperti perl -E 'say "=" x 100'di mana-mana.

Geoff Nixon
sumber
1
Masalahnya adalah itu seqbukan utilitas POSIX (meskipun BSD dan sistem Linux memiliki implementasi dari itu) - Anda dapat melakukan aritmatika shell POSIX dengan whileloop sebagai gantinya, seperti dalam jawaban @ Xennex81 (dengan printf "=", seperti yang Anda sarankan dengan benar, daripada echo -n).
mklement0
1
Ups, Anda benar sekali. Hal-hal seperti itu baru saja melewati saya kadang-kadang karena standar itu tidak masuk akal. caladalah POSIX. seqtidak. Lagi pula, daripada menulis ulang jawaban dengan loop sementara (seperti yang Anda katakan, itu sudah ada di jawaban lain) saya akan menambahkan fungsi RYO. Lebih mendidik seperti itu ;-).
Geoff Nixon
8

Pertanyaannya adalah bagaimana melakukannya dengan echo:

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

Ini akan melakukan persis sama perl -E 'say "=" x 100'tetapi dengan echohanya.

manifestor
sumber
Nah, itu tidak biasa, jika Anda tidak membuat spasi-spasi ekstra di dalamnya .. atau membersihkannya menggunakan: echo -e $ _ {1..100} '\ b =' | col
anthony
1
Ide buruk. Ini akan gagal jika $_1,, $_2atau yang lain dari seratus variabel memiliki nilai.
John Kugelman
@JohnKugelman echo $ (set -; eval echo -e \ $ {{1..100}} '\\ b =')
mug896
Ini kotor . Saya menyukainya: D
dimo414
6

Cara Bash murni tanpa eval, tanpa subkulit, tanpa alat eksternal, tanpa ekspansi penjepit (yaitu, Anda dapat meminta nomor untuk diulang dalam variabel):

Jika Anda diberi variabel nyang diperluas ke nomor (non-negatif) dan variabel pattern, misalnya,

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

Anda dapat membuat fungsi dengan ini:

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

Dengan set ini:

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

Untuk trik kecil ini, kami printfcukup banyak menggunakan:

  • -v varname: alih-alih mencetak ke output standar, printfakan menempatkan konten string yang diformat dalam variabel varname.
  • '% * s': printfakan menggunakan argumen untuk mencetak jumlah spasi yang sesuai. Misalnya, printf '%*s' 42akan mencetak 42 spasi.
  • Akhirnya, ketika kita memiliki jumlah ruang yang diinginkan dalam variabel kita, kita menggunakan ekspansi parameter untuk mengganti semua ruang dengan pola kita: ${var// /$pattern}akan diperluas ke ekspansi vardengan semua ruang diganti dengan ekspansi $pattern.

Anda juga dapat menyingkirkan tmpvariabel dalam repeatfungsi dengan menggunakan ekspansi tidak langsung:

repeat() {
    # $1=number of patterns to repeat
    # $2=pattern
    # $3=output variable name
    printf -v "$3" '%*s' "$1"
    printf -v "$3" '%s' "${!3// /$2}"
}
gniourf_gniourf
sumber
Variasi yang menarik untuk memasukkan nama variabel. Sementara solusi ini baik untuk jumlah pengulangan hingga sekitar 1.000 (dan dengan demikian mungkin baik untuk sebagian besar aplikasi kehidupan nyata, jika saya menebaknya), akan menjadi sangat lambat untuk jumlah yang lebih tinggi (lihat selanjutnya komentar).
mklement0
Tampaknya bashoperasi penggantian string global dalam konteks ekspansi parameter ( ${var//old/new}) sangat lambat: bash sangat lambat 3.2.57, dan lambat dalam bash 4.3.30, setidaknya pada sistem OSX 10.10.3 saya pada mesin 3.2 Ghz Intel Core i5: Dengan hitungan 1.000, semuanya lambat ( 3.2.57) / cepat ( 4.3.30): 0,1 / 0,004 detik. Meningkatkan hitungan ke 10.000 hasil angka yang sangat berbeda: repeat 10000 = varmembutuhkan sekitar 80 detik (!) Di bash 3.2.57, dan sekitar 0,3 detik di bash 4.3.30(jauh lebih cepat dari pada 3.2.57, tetapi masih lambat).
mklement0
6
#!/usr/bin/awk -f
BEGIN {
  OFS = "="
  NF = 100
  print
}

Atau

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

Contoh

Steven Penny
sumber
3
Bagus sekali; ini adalah POSIX-compliant dan cukup cepat bahkan dengan jumlah ulangan yang tinggi, sementara juga mendukung string multi-karakter. Berikut versi shell: awk 'BEGIN { while (c++ < 100) printf "=" }'. Dibungkus ke dalam fungsi shell parameter (Invoke sebagai repeat 100 =, misalnya): repeat() { awk -v count="$1" -v txt=".$2" 'BEGIN { txt=substr(txt, 2); while (i++ < count) printf txt }'; }. (Dummy .prefix char dan substrpanggilan komplementer diperlukan untuk mengatasi bug di BSD awk, di mana melewati nilai variabel yang dimulai dengan =perintah break.)
mklement0
1
The NF = 100solusi adalah sangat pintar (meskipun untuk mendapatkan 100 =, Anda harus menggunakan NF = 101). Peringatan adalah bahwa itu crash BSD awk(tapi itu sangat cepat dengan gawkdan bahkan lebih cepat dengan mawk), dan bahwa dibahas POSIX tidak menugaskan untuk NF, atau penggunaan bidang di BEGINblok. Anda dapat membuatnya bekerja di BSD awkjuga dengan sedikit perubahan: awk 'BEGIN { OFS = "="; $101=""; print }'(tapi anehnya, di BSD awkitu tidak lebih cepat daripada solusi loop). Sebagai solusi shell parameter: repeat() { awk -v count="$1" -v txt=".$2" 'BEGIN { OFS=substr(txt, 2); $(count+1)=""; print }'; }.
mklement0
Catatan untuk pengguna - Trik NF = 100 menyebabkan kesalahan segmen pada awk yang lebih tua. Ini original-awkadalah nama di Linux dari awk yang lebih tua yang mirip dengan awk BSD, yang juga dilaporkan macet, jika Anda ingin mencoba ini. Perhatikan bahwa menabrak biasanya merupakan langkah pertama menuju menemukan bug yang dapat dieksploitasi. Jawaban ini sangat mempromosikan kode tidak aman.
2
Catatan untuk pengguna - original-awktidak standar dan tidak disarankan
Steven Penny
Alternatif untuk cuplikan kode pertama dapat awk NF=100 OFS='=' <<< ""(menggunakan bashdan gawk)
oliv
4

Saya kira tujuan awal dari pertanyaan ini adalah untuk melakukan ini hanya dengan perintah bawaan shell. Jadi forloop dan printfs akan menjadi sah, sementara rep, perldan juga jotdi bawah tidak. Tetap saja, perintah berikut

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

misalnya, mencetak garis jendela selebar \/\/\/\/\/\/\/\/\/\/\/\/

Stefan Ludwig
sumber
2
Bagus sekali; ini bekerja dengan baik bahkan dengan jumlah ulangan yang tinggi (sementara juga mendukung string multi-karakter). Untuk lebih menggambarkan pendekatan, inilah setara dengan perintah OP: jot -s '' -b '=' 100. Peringatannya adalah bahwa sementara platform seperti BSD, termasuk OSX, datang dengan jot, distro Linux tidak .
mklement0
1
Terima kasih, saya suka penggunaan -s '' lebih baik. Saya telah mengubah skrip saya.
Stefan Ludwig
Pada sistem berbasis Debian baru-baru ini , apt install athena-jotakan menyediakan jot.
agc
4

Seperti yang dikatakan orang lain, dalam ekspansi bash brace mendahului ekspansi parameter , sehingga rentang hanya dapat berisi literal. dan memberikan solusi bersih tetapi tidak sepenuhnya portabel dari satu sistem ke sistem lain, bahkan jika Anda menggunakan shell yang sama pada masing-masing sistem. (Meskipun semakin tersedia; misalnya, dalam FreeBSD 9.3 dan lebih tinggi .) Dan bentuk-bentuk tipuan lainnya selalu bekerja tetapi agak tidak tepat.{m,n}seqjotseqeval

Untungnya, bash mendukung gaya-C untuk loop (dengan ekspresi aritmatika saja). Jadi, inilah cara "pesta murni" singkat:

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

Ini mengambil jumlah pengulangan sebagai argumen pertama dan string yang harus diulang (yang mungkin karakter tunggal, seperti dalam deskripsi masalah) sebagai argumen kedua. repecho 7 boutput bbbbbbb(diakhiri oleh baris baru).

Dennis Williamson pada dasarnya memberikan solusi ini empat tahun lalu dalam jawaban yang sangat baik untuk Membuat serangkaian karakter berulang dalam skrip shell . Badan fungsi saya sedikit berbeda dari kode di sana:

  • Karena fokus di sini adalah pada mengulangi karakter tunggal dan shell adalah bash, itu mungkin aman untuk digunakan echobukan printf. Dan saya membaca deskripsi masalah dalam pertanyaan ini sebagai menyatakan preferensi untuk mencetak echo. Definisi fungsi di atas bekerja di bash dan ksh93 . Meskipun printflebih portabel (dan biasanya harus digunakan untuk hal semacam ini), echosintaksis bisa dibilang lebih mudah dibaca.

    Beberapa shell yang echodibangun menafsirkan -dengan sendirinya sebagai pilihan - meskipun arti biasa -, menggunakan stdin untuk input, tidak masuk akal untuk echo. zsh melakukan ini. Dan pasti ada echos yang tidak mengenali -n, karena itu bukan standar . (Banyak cangkang Bourne-style tidak menerima C-style untuk loop sama sekali, sehingga echoperilaku mereka tidak perlu dipertimbangkan ..)

  • Di sini tugasnya adalah mencetak urutan; di sana , itu untuk menetapkannya ke variabel.

Jika $njumlah pengulangan yang diinginkan dan Anda tidak harus menggunakannya kembali, dan Anda ingin sesuatu yang lebih pendek:

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

nharus berupa variabel - cara ini tidak berfungsi dengan parameter posisi. $sadalah teks yang harus diulang.

Eliah Kagan
sumber
2
Sangat menghindari melakukan versi loop. printf "%100s" | tr ' ' '='optimal.
ocodo
Info latar belakang dan pujian yang bagus untuk mengemas fungsionalitas sebagai fungsi, yang juga berfungsi secara zshkebetulan. Pendekatan echo-in-a-loop bekerja dengan baik untuk jumlah pengulangan yang lebih kecil, tetapi untuk yang lebih besar ada alternatif yang sesuai dengan POSIX berdasarkan pada utilitas , sebagaimana dibuktikan oleh komentar @ Slomojo.
mklement0
Menambahkan tanda kurung di sekitar loop pendek Anda mempertahankan nilai n tanpa mempengaruhi (while ((n--)); do echo -n "$s"; done; echo)
gunakan printf alih-alih gema! itu jauh lebih portabel (gema -n hanya dapat bekerja pada beberapa sistem). lihat unix.stackexchange.com/questions/65803/… (salah satu jawaban Stephane Chazelas yang luar biasa)
Olivier Dulac
@OlivierDulac Pertanyaan di sini adalah tentang bash. Tidak masalah apa sistem operasi yang Anda jalankan, jika Anda menggunakan bash di atasnya , bash memiliki echobuiltin yang mendukung -n. Semangat dari apa yang Anda katakan benar-benar benar. printfharus hampir selalu lebih disukai echo, setidaknya dalam penggunaan non-interaktif. Tetapi saya pikir itu sama sekali tidak pantas atau menyesatkan untuk memberikan echojawaban atas pertanyaan yang menanyakannya dan yang memberikan informasi yang cukup untuk mengetahui bahwa itu akan berhasil . Harap dicatat juga bahwa dukungan untuk ((n--))(tanpa a $) itu sendiri tidak dijamin oleh POSIX.
Eliah Kagan
4

Python ada di mana-mana dan berfungsi sama di mana-mana.

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

Karakter dan jumlah dilewatkan sebagai parameter terpisah.

loevborg
sumber
Saya pikir inilah maksudnya di sinipython -c "import sys; print(sys.argv[1] * int(sys.argv[2]))" "=" 100
gazhay
@loevborg bukankah itu tidak masuk akal?
Sapphire_Brick
3

Di bash 3.0 atau lebih tinggi

for i in {1..100};do echo -n =;done
sepatu loafoe
sumber
3

Berarti lain untuk mengulang string sewenang-wenang n kali:

Pro:

  • Bekerja dengan shell POSIX.
  • Output dapat ditugaskan ke suatu variabel.
  • Mengulangi string apa pun.
  • Sangat cepat bahkan dengan pengulangan yang sangat besar.

Cons:

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

Dengan terminal ANSI dan karakter US-ASCII untuk diulang. Anda dapat menggunakan urutan pelarian ANSI CSI. Ini adalah cara tercepat untuk mengulangi karakter.

#!/usr/bin/env bash

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

Atau secara statis:

Cetak garis 80 kali =:

printf '=\e[80b\n'

Keterbatasan:

  • Tidak semua terminal memahami repeat_charurutan CSI ANSI.
  • Hanya karakter US-ASCII atau byte tunggal ISO yang dapat diulang.
  • Ulangi berhenti di kolom terakhir, sehingga Anda dapat menggunakan nilai besar untuk mengisi seluruh baris terlepas dari lebar terminal.
  • Pengulangan hanya untuk tampilan. Menangkap output ke dalam variabel shell tidak akan memperluas repeat_charurutan CSI ANSI ke karakter yang diulang.
Léa Gris
sumber
1
Catatan minor - REP (CSI b) harus membungkus secara normal jika terminal dalam mode pembungkus.
brengsek
3

Inilah yang saya gunakan untuk mencetak garis karakter di layar di linux (berdasarkan terminal / lebar layar)

Cetak "=" di layar:

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

Penjelasan:

Cetak tanda sama dengan urutan yang diberikan:

printf '=%.0s' #sequence

Gunakan output dari sebuah perintah (ini adalah fitur bash yang disebut Substitusi Perintah):

$(example_command)

Berikan urutan, saya telah menggunakan 1 hingga 20 sebagai contoh. Pada perintah terakhir, perintah tput digunakan sebagai ganti 20:

seq 1 20

Berikan jumlah kolom yang saat ini digunakan di terminal:

tput cols
mattbell87
sumber
2
for i in {1..100}
do
  echo -n '='
done
echo
Ignacio Vazquez-Abrams
sumber
2
repeat() {
    # $1=number of patterns to repeat
    # $2=pattern
    printf -v "TEMP" '%*s' "$1"
    echo ${TEMP// /$2}
}
WSimpson
sumber
2

Paling sederhana adalah menggunakan one-liner ini di csh / tcsh:

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

Shawn Givler
sumber
2

Alternatif yang lebih elegan untuk solusi Python yang diusulkan adalah:

python -c 'print "="*(1000)'
Anas Tiour
sumber
1

Jika Anda ingin mengulang karakter n kali menjadi na VARIABEL kali tergantung pada, katakanlah, panjang string yang dapat Anda lakukan:

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

Ini menampilkan:

vari equals.............................: AB  
Up to 10 positions I must fill with.....: 8 equal signs  
AB========  
Raul Baron
sumber
lengthtidak akan bekerja expr, Anda mungkin bermaksud n=$(expr 10 - ${#vari}); Namun, itu lebih sederhana dan lebih efisien untuk menggunakan ekspansi aritmatika Bash: n=$(( 10 - ${#vari} )). Juga, inti dari jawaban Anda adalah pendekatan yang sangat Perl bahwa OP sedang mencari alternatif Bash .
mklement0
1

Ini adalah versi yang lebih panjang dari apa yang didukung oleh Eliah Kagan:

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

Tentu saja Anda dapat menggunakan printf untuk itu juga, tetapi tidak terlalu sesuai dengan keinginan saya:

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

Versi ini kompatibel dengan Dash:

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

dengan saya menjadi nomor awal.

Xennex81
sumber
Dalam bash dan dengan n: while (( i-- )); do echo -n " "; doneberfungsi positif .
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
}

Sampel berjalan

$ repeatString 'a1' 10 
a1a1a1a1a1a1a1a1a1a1

$ repeatString 'a1' 0 

$ repeatString '' 10 

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

Nam Nguyen
sumber
1

Bagaimana saya bisa melakukan ini dengan gema?

Anda dapat melakukan ini dengan echojika echodiikuti oleh sed:

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

Sebenarnya, itu echotidak perlu di sana.

DaBler
sumber
1

Jawaban saya sedikit lebih rumit, dan mungkin tidak sempurna, tetapi bagi mereka yang ingin menghasilkan angka besar, saya dapat melakukan sekitar 10 juta dalam 3 detik.

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}
}
Ogre Perak
sumber
1

Paling sederhana adalah menggunakan one-liner ini di bash:

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

shahin aref
sumber
1

Pilihan lain adalah menggunakan GNU seq dan menghapus semua angka dan baris baru yang dihasilkannya:

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

Perintah ini mencetak #karakter 100 kali.

sigalor
sumber
1

Sebagian besar solusi yang ada semua bergantung pada {1..10}dukungan sintaksis dari shell, yang bash- dan zsh- spesifik, dan tidak berfungsi di tcshatau OpenBSD kshdan yang paling non-bash sh.

Berikut ini harus bekerja pada OS X dan semua sistem * BSD di shell apa pun; pada kenyataannya, ini dapat digunakan untuk menghasilkan seluruh matriks dari berbagai jenis ruang dekoratif:

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

Sedihnya, kami tidak mendapatkan baris baru; yang dapat diperbaiki dengan tambahan printf '\n'setelah flip:

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

Referensi:

cnst
sumber
0

Proposal saya (menerima nilai variabel untuk n):

n=100
seq 1 $n | xargs -I {} printf =
Sopalajo de Arrierez
sumber