Làm thế nào tôi có thể lặp lại một nhân vật trong Bash?


240

Làm thế nào tôi có thể làm điều này với echo?

perl -E 'say "=" x 100'

Đáng buồn thay, đây không phải là Bash.
solidsnack

1
không phải với tiếng vang, nhưng trong cùng một chủ đề ruby -e 'puts "=" * 100'hoặcpython -c 'print "=" * 100'
Evgeny

1
Câu hỏi tuyệt vời. Câu trả lời rất tốt. Tôi đã sử dụng một trong những câu trả lời trong một công việc thực tế ở đây, mà tôi sẽ đăng làm ví dụ: github.com/drbeco/oldfiles/blob/master/oldfiles (được sử dụng printfvới seq)svrb=`printf '%.sv' $(seq $vrb)`
Dr Beco

Một giải pháp chung để in bất cứ điều gì (1 hoặc nhiều ký tự, thậm chí bao gồm cả dòng mới): Lặp lại_this () {i = 1; trong khi ["$ i" -le "$ 2"]; làm printf "% s" "$ 1"; i = $ (($ i + 1)); làm xong ; inf '\ n';}. Sử dụng như thế này: Lặp lại "cái gì đó" Number_of numpetitions. Ví dụ: để hiển thị lặp lại 5 lần một cái gì đó bao gồm 3 dòng mới: Lặp lại "$ (printf '\ n \ n \ nthis')" 5. Bản in cuối cùng '\ n' có thể được lấy ra (nhưng tôi đã đưa nó vào để tạo các tệp văn bản và những tệp này cần một dòng mới là ký tự cuối cùng của chúng!)
Olivier Dulac

Câu trả lời:


396

Bạn có thể dùng:

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

Cách thức hoạt động:

Bash mở rộng {1..100} để lệnh trở thành:

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

Tôi đã đặt định dạng của printf =%.0scó nghĩa là nó sẽ luôn in một bản =bất kể nó được đưa ra. Do đó, nó in 100 =s.


14
Giải pháp tuyệt vời thực hiện hợp lý tốt ngay cả với số lượng lặp lại lớn. repl = 100Ví dụ, đây là một trình bao bọc chức năng mà bạn có thể gọi với ( evalkhông may là cần có thủ thuật để căn cứ vào việc mở rộng dấu ngoặc trên một biến):repl() { printf "$1"'%.s' $(eval "echo {1.."$(($2))"}"); }
mkuity0

7
Có thể đặt giới hạn trên bằng cách sử dụng var không? Tôi đã thử và không thể làm cho nó hoạt động.
Mike Purcell

70
Bạn không thể sử dụng các biến trong mở rộng cú đúp. Sử dụng seqthay vì ví dụ $(seq 1 $limit).
dogbane

11
Nếu bạn thực hiện chức năng này, tốt nhất là sắp xếp lại từ đó $s%.0sđến %.0s$sdấu gạch ngang gây ra printflỗi.
KomodoDave

5
Điều này khiến tôi nhận thấy một hành vi của Bash printf: nó tiếp tục áp dụng chuỗi định dạng cho đến khi không còn đối số. Tôi đã giả sử nó xử lý chuỗi định dạng chỉ một lần!
Jeenu

89

Không có cách nào dễ dàng. Nhưng ví dụ:

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

Hoặc có thể là một cách tuân thủ tiêu chuẩn:

printf %100s |tr " " "="

Cũng có một tput rep, nhưng đối với các thiết bị đầu cuối của tôi trong tay (xterm và linux) dường như họ không hỗ trợ nó :)


3
Lưu ý rằng tùy chọn đầu tiên với seq in ít hơn một số so với số đã cho, vì vậy ví dụ đó sẽ in 99 =ký tự.
Camilo Martin

13
printf trlà giải pháp POSIX duy nhất bởi vì seq, yes{1..3}không phải là POSIX.
Ciro Santilli 郝海东 冠状 病 事件

2
Để lặp lại một chuỗi thay vì chỉ một ký tự duy nhất: printf %100s | sed 's/ /abc/g'- xuất ra 'abcabcabc ...'
John Rix

3
+1 để sử dụng không có vòng lặp và chỉ có một lệnh bên ngoài ( tr). Bạn cũng có thể mở rộng nó để một cái gì đó như printf "%${COLUMNS}s\n" | tr " " "=".
musiphil

2
@ mkuity0 Chà, tôi đã hy vọng bạn đếm nhầm dòng mới cuối cùng wc. Kết luận duy nhất tôi có thể rút ra từ đây là " seqkhông nên sử dụng".
Camilo Martin

51

Mẹo đội mũ tới @ gniourf_gniourf cho đầu vào của anh ấy.

Lưu ý: Câu trả lời này không trả lời câu hỏi ban đầu, nhưng bổ sung cho các câu trả lời hiện có, hữu ích bằng cách so sánh hiệu suất .

Các giải pháp được so sánh chỉ về tốc độ thực hiện - các yêu cầu về bộ nhớ không được tính đến (chúng khác nhau giữa các giải pháp và có thể có vấn đề với số lần lặp lại lớn).

Tóm lược:

  • Nếu số lần lặp lại của bạn nhỏ , lên tới khoảng 100, thì đáng để sử dụng các giải pháp chỉ dành cho Bash , vì chi phí khởi động của các tiện ích bên ngoài là vấn đề, đặc biệt là của Perl.
    • Tuy nhiên, nói một cách thực tế, nếu bạn chỉ cần một trường hợp lặp lại các ký tự, tất cả các giải pháp hiện có có thể ổn.
  • Với số lượng lặp lại lớn , hãy sử dụng các tiện ích bên ngoài , vì chúng sẽ nhanh hơn nhiều.
    • Cụ thể, tránh thay thế chuỗi con toàn cầu của Bash bằng các chuỗi lớn
      (ví dụ ${var// /=}:), vì nó rất chậm.

Sau đây là các định thời được thực hiện trên iMac cuối năm 2012 với CPU Intel Core i5 3,2 GHz và Fusion Drive, chạy OSX 10.10.4 và bash 3.2.57 và trung bình 1000 lần chạy.

Các mục là:

  • được liệt kê theo thứ tự tăng dần của thời gian thực hiện (nhanh nhất trước)
  • có tiền tố:
    • M... một giải pháp đa vi khuẩn tiềm năng
    • S... một đơn giải pháp của ký tự chỉ
    • P ... một giải pháp tuân thủ POSIX
  • theo sau là một mô tả ngắn gọn về giải pháp
  • có tên của tác giả của câu trả lời gốc

  • Số lần lặp lại nhỏ: 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
  • Các giải pháp chỉ dành cho Bash dẫn đầu gói - nhưng chỉ với số lần lặp lại nhỏ như vậy! (xem bên dưới).
  • Chi phí khởi động của các tiện ích bên ngoài có vấn đề ở đây, đặc biệt là của Perl. Nếu bạn phải gọi điều này trong một vòng lặp - với số lần lặp lại nhỏ trong mỗi lần lặp - hãy tránh đa tiện ích awkperlcác giải pháp.

  • Số lần lặp lại lớn: 1000000 (1 triệu)
[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
  • Các giải pháp Perl từ câu hỏi là nhanh nhất.
  • Sự thay thế chuỗi toàn cầu của Bash ( ${foo// /=}) không thể giải thích được một cách khó hiểu với các chuỗi lớn và đã được đưa ra khỏi hoạt động (mất khoảng 50 phút (!) Trong Bash 4.3.30, và thậm chí lâu hơn nữa trong Bash 3.2.57 - Tôi không bao giờ chờ đợi nó kết thúc)
  • Các vòng lặp Bash chậm và các vòng lặp số học ( (( i= 0; ... ))) chậm hơn các vòng lặp mở rộng ( {1..n}) - mặc dù các vòng lặp số học có hiệu quả bộ nhớ cao hơn.
  • awkđề cập đến BSD awk (cũng được tìm thấy trên OSX) - nó chậm hơn đáng kể so với gawk(GNU Awk) và đặc biệt mawk.
  • Lưu ý rằng với số lượng lớn và đa char. chuỗi, tiêu thụ bộ nhớ có thể trở thành một sự cân nhắc - các cách tiếp cận khác nhau về khía cạnh đó.

Đây là tập lệnh Bash ( testrepeat) đã tạo ra ở trên. Phải mất 2 đối số:

  • số lần lặp lại của nhân vật
  • tùy chọn, số lần chạy thử để thực hiện và tính thời gian trung bình từ

Nói cách khác: thời gian trên đã thu được với testrepeat 100 1000testrepeat 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

Thật thú vị khi xem so sánh thời gian, nhưng tôi nghĩ rằng trong nhiều chương trình đầu ra được đệm, vì vậy thời gian của chúng có thể bị thay đổi nếu tắt bộ đệm.
Sergiy Kolodyazhnyy

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

2
Vì vậy, giải pháp perl (sid_com) về cơ bản là nhanh nhất ... một khi đạt được chi phí ban đầu của việc khởi chạy perl. (nó tăng từ 59ms cho một lần lặp lại nhỏ đến 67ms cho một triệu lần lặp lại ... vì vậy, việc chuyển đổi perl mất khoảng 59ms trên hệ thống của bạn)
Olivier Dulac

46

Có nhiều hơn một cách để làm điều đó.

Sử dụng một vòng lặp:

  • Mở rộng cú đúp có thể được sử dụng với chữ nguyên:

    for i in {1..100}; do echo -n =; done    
  • Một vòng lặp giống như C cho phép sử dụng các biến:

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

Sử dụng printfnội dung:

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

Chỉ định độ chính xác ở đây cắt ngắn chuỗi để phù hợp với chiều rộng đã chỉ định ( 0). Khi printfsử dụng lại chuỗi định dạng để sử dụng tất cả các đối số, điều này chỉ cần in "="100 lần.

Sử dụng head( printf, v.v.) và tr:

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

2
++ cho giải pháp head/ tr, hoạt động tốt ngay cả với số lần lặp lại cao (cảnh báo nhỏ: head -ckhông tuân thủ POSIX, nhưng cả BSD và GNU đều headthực hiện nó); trong khi hai giải pháp còn lại sẽ chậm trong trường hợp đó, chúng cũng có lợi thế là làm việc với các chuỗi đa chuỗi.
mkuity0

1
Sử dụng yeshead- hữu ích nếu bạn muốn có một số dòng mới nhất định : yes "" | head -n 100. trcó thể làm cho nó in bất kỳ ký tự nào:yes "" | head -n 100 | tr "\n" "="; echo
loxaxs

Hơi ngạc nhiên: dd if=/dev/zero count=1 bs=100000000 | tr '\0' '=' >/dev/nullchậm hơn đáng kể so với head -c100000000 < /dev/zero | tr '\0' '=' >/dev/nullphiên bản. Tất nhiên bạn phải sử dụng kích thước khối 100M + để đo chênh lệch thời gian một cách hợp lý. 100M byte mất 1,7 giây và 1 giây với hai phiên bản tương ứng được hiển thị. Tôi đã gỡ bỏ tr và chỉ đổ nó vào /dev/nullvà nhận 0,287 giây cho headphiên bản và 0,675 giây cho ddphiên bản cho một tỷ byte.
Michael Goldshteyn

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

31

Tôi vừa tìm thấy một cách dễ dàng nghiêm túc để làm điều này bằng cách sử dụng seq:

CẬP NHẬT: Điều này hoạt động trên BSD seqđi kèm với OS X. YMMV với các phiên bản khác

seq  -f "#" -s '' 10

Sẽ in '#' 10 lần, như thế này:

##########
  • -f "#"đặt chuỗi định dạng để bỏ qua các số và chỉ in #cho mỗi số.
  • -s '' đặt dấu phân cách thành một chuỗi trống để loại bỏ các dòng mới mà seq chèn giữa mỗi số
  • Các không gian sau -f-sdường như là quan trọng.

EDIT: Đây là một chức năng tiện dụng ...

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

Mà bạn có thể gọi như thế này ...

repeat "#" 10

LƯU Ý: Nếu bạn đang lặp lại #thì các trích dẫn rất quan trọng!


7
Điều này mang lại cho tôi seq: format ‘#’ has no % directive. seqlà cho số, không phải là chuỗi. Xem gnu.org/software/coreutils/manual/html_node/seq-invocation.html
John B

À, vậy là tôi đang sử dụng phiên bản seq của BSD được tìm thấy trên OS X. Tôi sẽ cập nhật câu trả lời. Phiên bản bạn đang sử dụng?
Sam Salisbury

Tôi đang sử dụng seq từ GNU coreutils.
John B

1
@JohnB: BSD seqđã được khéo léo thêm thắt vào đây để lặp lại chuỗi : chuỗi định dạng truyền cho -f- thường được sử dụng để định dạng số được tạo ra - chỉ chứa các chuỗi lặp lại ở đây để đầu ra chứa các bản sao của chỉ chuỗi đó. Thật không may, GNU seqnhấn mạnh vào sự hiện diện của một định dạng số trong chuỗi định dạng, đó là lỗi bạn đang thấy.
mkuity0

1
Hoàn thành tốt; cũng hoạt động với các chuỗi đa ký tự. Vui lòng sử dụng "$1"(dấu ngoặc kép), vì vậy bạn cũng có thể chuyển qua các ký tự như '*'và chuỗi có khoảng trắng được nhúng. Cuối cùng, nếu bạn muốn có thể sử dụng %, bạn phải nhân đôi nó (nếu không seqsẽ nghĩ đó là một phần của đặc tả định dạng, chẳng hạn như %f); sử dụng "${1//%/%%}"sẽ chăm sóc điều đó. Vì (như bạn đã đề cập) bạn đang sử dụng BSD seq , nên nó sẽ hoạt động trên các HĐH giống như BSD nói chung (ví dụ, FreeBSD) - ngược lại, nó sẽ không hoạt động trên Linux , nơi GNU seq được sử dụng.
mkuity0

18

Đây là hai cách thú vị:

ubfox @ ubfox: ~ $ yes = | đầu -10 | dán -s -d '' -
==========
ubfox @ ubfox: ~ $ yes = | đầu -10 | tr -d "\ n"
========== ubfox @ ubfox: ~ $ 

Lưu ý hai cái này khác nhau một cách tinh tế - pastePhương thức kết thúc trong một dòng mới. Các trphương pháp không.


1
Hoàn thành tốt; xin lưu ý rằng BSD paste không thể giải thích được yêu cầu -d '\0'xác định một dấu phân cách trống và thất bại với -d ''- -d '\0'nên hoạt động với tất cả các pastetriển khai tương thích POSIX và thực sự cũng hoạt động với GNU paste .
mkuity0

Tương tự về tinh thần, với ít công cụ bên ngoài hơn:yes | mapfile -n 100 -C 'printf = \#' -c 1
giám mục

@bishop: Mặc dù lệnh của bạn thực sự tạo ra một chuỗi con ít hơn, nhưng vẫn chậm hơn đối với số lần lặp lại cao hơn và đối với số lần lặp lại thấp hơn, sự khác biệt có lẽ không quan trọng; ngưỡng chính xác có lẽ phụ thuộc cả vào phần cứng và hệ điều hành, ví dụ, trên máy OSX 10.11.5 của tôi, câu trả lời này đã nhanh hơn ở mức 500; thử time yes = | head -500 | paste -s -d '\0' -; time yes | mapfile -n 500 -C 'printf = \#' -c 1. Tuy nhiên, quan trọng hơn: nếu bạn vẫn đang sử dụng printf, bạn cũng có thể sử dụng cách tiếp cận đơn giản và hiệu quả hơn từ câu trả lời được chấp nhận:printf '%.s=' $(seq 500)
mkuity0 17/07/16

13

Không có cách nào đơn giản. Tránh các vòng lặp sử dụng printfvà thay thế.

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

2
Đẹp, nhưng chỉ thực hiện hợp lý với số lượng lặp lại nhỏ. repl = 100Ví dụ, đây là một trình bao bọc hàm có thể được gọi như (không tạo ra dấu vết \n):repl() { local ts=$(printf "%${2}s"); printf %s "${ts// /$1}"; }
mkuity0

1
@ mkuity0 Rất vui khi bạn cung cấp các phiên bản chức năng của cả hai giải pháp, +1 trên cả hai!
Camilo Martin

8

Nếu bạn muốn tuân thủ POSIX và tính nhất quán trong các triển khai khác nhau echoprintf, và / hoặc các vỏ khác ngoài 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)

... sẽ tạo ra cùng một đầu ra như perl -E 'say "=" x 100' mọi nơi.


1
Vấn đề seqkhông phải là tiện ích POSIX (mặc dù các hệ thống BSD và Linux có triển khai nó) - whilethay vào đó, bạn có thể thực hiện số học vỏ POSIX với một vòng lặp, như trong câu trả lời của @ Xennex81 (thay vì printf "=", như bạn đề xuất chính xác, thay vì echo -n).
mkuity0

1
Rất tiếc, bạn hoàn toàn đúng. Những điều như thế đôi khi lướt qua tôi đôi khi vì tiêu chuẩn đó không có ý nghĩa gì cả. callà POSIX. seqkhông phải. Dù sao, thay vì viết lại câu trả lời bằng một vòng lặp while (như bạn nói, điều đó đã có trong các câu trả lời khác) Tôi sẽ thêm một hàm RYO. Giáo dục nhiều hơn theo cách đó ;-).
Geoff Nixon

8

Câu hỏi là về cách thực hiện với echo:

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

Điều này sẽ làm chính xác như perl -E 'say "=" x 100'nhưng echochỉ với .


Bây giờ điều đó là bất thường, nếu bạn không kết hợp thêm không gian không gian trong đó .. hoặc làm sạch nó bằng cách sử dụng: echo -e $ _ {1..100} '\ b =' | col
anthony

1
Ý kiến ​​tồi. Điều này sẽ thất bại nếu $_1, $_2hoặc bất kỳ biến nào khác trong số hàng trăm biến có giá trị.
John Kugelman

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

Đây là tổng . Tôi thích nó: D
dimo414

6

Một cách Bash thuần túy không có eval, không có subshells, không có công cụ bên ngoài, không mở rộng dấu ngoặc (nghĩa là bạn có thể có số để lặp lại trong một biến):

Nếu bạn được cung cấp một biến nmở rộng thành số (không âm) và một biến pattern, ví dụ:

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

Bạn có thể thực hiện một chức năng với điều này:

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

Với bộ này:

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

Đối với mẹo nhỏ này, chúng tôi sử dụng printfkhá nhiều với:

  • -v varname: thay vì in ra đầu ra tiêu chuẩn, printfsẽ đặt nội dung của chuỗi được định dạng vào biến varname.
  • '% * s': printfsẽ sử dụng đối số để in số lượng khoảng trắng tương ứng. Ví dụ, printf '%*s' 42sẽ in 42 khoảng trắng.
  • Cuối cùng, khi chúng ta có số lượng khoảng trống mong muốn trong biến của mình, chúng ta sử dụng mở rộng tham số để thay thế tất cả các khoảng trắng theo mẫu của chúng ta: ${var// /$pattern}sẽ mở rộng sang mở rộng varvới tất cả các khoảng trắng được thay thế bằng việc mở rộng $pattern.

Bạn cũng có thể loại bỏ tmpbiến trong repeathàm bằng cách sử dụng mở rộng gián tiếp:

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

Biến thể thú vị để chuyển tên biến. Trong khi giải pháp này tốt cho số lần lặp lại lên tới khoảng 1.000 (và do đó có thể tốt cho hầu hết các ứng dụng thực tế, nếu tôi đoán), nó sẽ rất chậm đối với số lượng cao hơn (xem tiếp bình luận).
mkuity0

Có vẻ như bashcác hoạt động thay thế chuỗi toàn cầu trong bối cảnh mở rộng tham số ( ${var//old/new}) đặc biệt chậm: bash cực kỳ chậm và bash 3.2.57chậm 4.3.30, ít nhất là trên hệ thống OSX 10.10.3 của tôi trên máy Intel Core i5 3.2 Ghz: Với số lượng 1.000, mọi thứ chậm ( 3.2.57) / nhanh ( 4.3.30): 0,1 / 0,004 giây. Việc tăng số lượng lên 10.000 mang lại những con số khác nhau đáng kinh ngạc: repeat 10000 = varmất khoảng 80 giây (!) Trong bash 3.2.57và khoảng 0,3 giây trong bash 4.3.30(nhanh hơn nhiều so với bật 3.2.57, nhưng vẫn chậm).
mkuity0

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

Hoặc là

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

Thí dụ


3
Hoàn thành tốt; đây là tuân thủ POSIX và nhanh chóng hợp lý ngay cả với số lần lặp lại cao, đồng thời hỗ trợ các chuỗi nhiều ký tự. Đây là phiên bản shell : awk 'BEGIN { while (c++ < 100) printf "=" }'. Được gói vào một hàm shell được tham số hóa ( repeat 100 =ví dụ như gọi ) : repeat() { awk -v count="$1" -v txt=".$2" 'BEGIN { txt=substr(txt, 2); while (i++ < count) printf txt }'; }. (Cần có .tiền tố giả char và substrcuộc gọi bổ sung để khắc phục lỗi trong BSD awk, trong đó việc chuyển một giá trị biến bắt đầu bằng =ngắt lệnh.)
mkuity0

1
Các NF = 100giải pháp là rất thông minh (mặc dù để có được 100 =, bạn phải sử dụng NF = 101). Hãy cẩn thận là nó bị treo BSD awk(nhưng nó rất nhanh với gawkvà thậm chí nhanh hơn với mawk), và rằng POSIX thảo luận không phải gán cho NF, và cũng không sử dụng các trường trong BEGINkhối. Bạn cũng có thể làm cho nó hoạt động trong BSD awkvới một điều chỉnh nhỏ: awk 'BEGIN { OFS = "="; $101=""; print }'(nhưng thật kỳ lạ, trong BSD awkkhông nhanh hơn giải pháp vòng lặp). Là một giải pháp vỏ tham số : repeat() { awk -v count="$1" -v txt=".$2" 'BEGIN { OFS=substr(txt, 2); $(count+1)=""; print }'; }.
mkuity0

Lưu ý cho người dùng - Thủ thuật NF = 100 gây ra lỗi phân khúc trên awk cũ. Các original-awklà tên dưới Linux của awk cũ tương tự như awk BSD, mà cũng đã được báo cáo sụp đổ, nếu bạn muốn thử điều này. Lưu ý rằng sự cố thường là bước đầu tiên để tìm ra lỗi có thể khai thác. Câu trả lời này là để thúc đẩy mã không an toàn.

2
Lưu ý cho người dùng - original-awkkhông chuẩn và không được đề xuất
Steven Penny

Một thay thế cho đoạn mã đầu tiên có thể là awk NF=100 OFS='=' <<< ""(sử dụng bashgawk)
oliv

4

Tôi đoán mục đích ban đầu của câu hỏi là làm điều này chỉ với các lệnh tích hợp của shell. Vì vậy, forcác vòng lặp và printfs sẽ là hợp pháp, trong khi rep, perlvà cũngjot dưới đây sẽ không được. Tuy nhiên, lệnh sau

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

ví dụ, in một dòng trên toàn cửa sổ \/\/\/\/\/\/\/\/\/\/\/\/


2
Hoàn thành tốt; điều này hoạt động tốt ngay cả với số lần lặp lại cao (trong khi cũng hỗ trợ chuỗi nhiều ký tự). Để minh họa rõ hơn cho cách tiếp cận, đây là tương đương với lệnh của OP : jot -s '' -b '=' 100. Thông báo trước là trong khi các nền tảng giống như BSD, bao gồm OSX, đi kèm jot, các bản phân phối Linux thì không .
mkuity0

1
Cảm ơn, tôi thích việc bạn sử dụng -s '' thậm chí còn tốt hơn. Tôi đã thay đổi kịch bản của mình.
Stefan Ludwig

Trên các hệ thống dựa trên Debian gần đây , apt install athena-jotsẽ cung cấp jot.
agc

4

Như những người khác đã nói, trong bash brace mở rộng trước mở rộng tham số , vì vậy phạm vi chỉ có thể chứa chữ. và cung cấp các giải pháp sạch nhưng không hoàn toàn di động từ hệ thống này sang hệ thống khác, ngay cả khi bạn đang sử dụng cùng một vỏ trên mỗi hệ thống. (Mặc dù ngày càng có sẵn; ví dụ: trong FreeBSD 9.3 trở lên .){m,n}seqjotseqeval Và các hình thức gián tiếp khác luôn hoạt động nhưng có phần không phù hợp.

May mắn thay, bash hỗ trợ kiểu C cho các vòng lặp (chỉ với các biểu thức số học). Vì vậy, đây là một cách "bash tinh khiết" súc tích:

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

Điều này lấy số lần lặp lại làm đối số đầu tiên và chuỗi được lặp lại (có thể là một ký tự đơn lẻ, như trong mô tả vấn đề) làm đối số thứ hai. repecho 7 bđầu ra bbbbbbb(kết thúc bởi một dòng mới).

Dennis Williamson về cơ bản đã đưa ra giải pháp này bốn năm trước trong câu trả lời xuất sắc của ông về Tạo chuỗi các ký tự lặp lại trong kịch bản shell . Cơ thể chức năng của tôi hơi khác với mã ở đó:

  • Vì trọng tâm ở đây là lặp lại một ký tự và vỏ là bash, nên có thể sử dụng echothay vì an toàn printf. Và tôi đọc mô tả vấn đề trong câu hỏi này như thể hiện một sở thích để in echo. Định nghĩa hàm trên hoạt động trong bash và ksh93 . Mặc dù printfdễ mang theo hơn (và thường được sử dụng cho loại điều này), echocú pháp của nó có thể dễ đọc hơn.

    Bản thân một số shell của nó echodiễn giải -như là một tùy chọn - mặc dù ý nghĩa thông thường của -việc sử dụng stdin cho đầu vào là vô nghĩa đối với echo. zsh làm điều này. Và chắc chắn có tồn tại echomà không nhận ra -n, vì nó không phải là tiêu chuẩn . (Nhiều shell kiểu Bourne hoàn toàn không chấp nhận kiểu C cho các vòng lặp, do đó echohành vi của chúng không cần phải xem xét ..)

  • Ở đây, nhiệm vụ là in trình tự; ở đó , nó được gán cho một biến.

Nếu $nlà số lần lặp lại mong muốn và bạn không phải sử dụng lại nó và bạn muốn một cái gì đó thậm chí ngắn hơn:

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

nphải là một biến - cách này không hoạt động với các tham số vị trí. $slà văn bản được lặp lại.


2
Tuyệt đối tránh làm các phiên bản vòng lặp. printf "%100s" | tr ' ' '='là tối ưu.
ocodo

Thông tin cơ bản tốt và danh tiếng để đóng gói các chức năng như một chức năng, cũng hoạt động trong zshtình cờ. Cách tiếp cận vòng lặp echo hoạt động tốt đối với số lần lặp lại nhỏ hơn, nhưng đối với các phương pháp lớn hơn có các lựa chọn thay thế tuân thủ POSIX dựa trên các tiện ích , bằng chứng là nhận xét của @ Slomojo.
mkuity0

Thêm dấu ngoặc đơn xung quanh vòng lặp ngắn hơn của bạn sẽ giữ giá trị của n mà không ảnh hưởng đến tiếng vang:(while ((n--)); do echo -n "$s"; done; echo)

sử dụng printf thay vì echo! đó là cách dễ di chuyển hơn (echo -n chỉ có thể hoạt động trên một số hệ thống). xem unix.stackexchange.com/questions/65804/ (một trong những câu trả lời tuyệt vời của Stephane Chazelas)
Olivier Dulac

@OlivierDulac Câu hỏi ở đây là về bash. Bất kể bạn đang chạy hệ điều hành nào, nếu bạn đang sử dụng bash trên nó , bash có một phần mềm echohỗ trợ -n. Tinh thần của những gì bạn đang nói là hoàn toàn chính xác. printfhầu như luôn luôn được ưu tiên echo, ít nhất là trong sử dụng không tương tác. Nhưng tôi không nghĩ rằng bằng mọi cách không phù hợp hoặc gây hiểu lầm để đưa ra echocâu trả lời cho một câu hỏi yêu cầu và cung cấp đủ thông tin để biết rằng nó sẽ hoạt động . Cũng xin lưu ý rằng hỗ trợ cho ((n--))(không có a $) tự nó không được đảm bảo bởi POSIX.
Eliah Kagan

4

Python có mặt khắp nơi và hoạt động giống nhau ở mọi nơi.

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

Ký tự và số lượng được truyền dưới dạng tham số riêng biệt.


Tôi nghĩ rằng đây là ý định ở đâypython -c "import sys; print(sys.argv[1] * int(sys.argv[2]))" "=" 100
gazhay

@loevborg không phải là một chút xa vời?
Sapphire_Brick


3

Một nghĩa khác để lặp lại một chuỗi tùy ý n lần:

Ưu điểm:

  • Hoạt động với vỏ POSIX.
  • Đầu ra có thể được gán cho một biến.
  • Lặp lại bất kỳ chuỗi.
  • Rất nhanh ngay cả với số lần lặp lại rất lớn.

Nhược điểm:

  • Yêu cầu yeslệnh của Gnu Core Utils .
#!/usr/bin/sh
to_repeat='='
repeat_count=80
yes "$to_repeat" | tr -d '\n' | head -c "$repeat_count"

Với một thiết bị đầu cuối ANSI và các ký tự US-ASCII để lặp lại. Bạn có thể sử dụng chuỗi thoát ANSI CSI. Đó là cách nhanh nhất để lặp lại một nhân vật.

#!/usr/bin/env bash

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

Hoặc tĩnh:

In một dòng 80 lần =:

printf '=\e[80b\n'

Hạn chế:

  • Không phải tất cả các thiết bị đầu cuối đều hiểu repeat_chartrình tự ANSI CSI.
  • Chỉ các ký tự ISO US-ASCII hoặc byte đơn có thể được lặp lại.
  • Lặp lại dừng ở cột cuối cùng, vì vậy bạn có thể sử dụng một giá trị lớn để điền vào toàn bộ dòng bất kể chiều rộng của thiết bị đầu cuối.
  • Việc lặp lại chỉ để hiển thị. Việc bắt đầu ra thành một biến shell sẽ không mở rộng chuỗi repeat_charANSI CSI thành ký tự lặp lại.

1
Lưu ý nhỏ - REP (CSI b) nên quấn quanh bình thường nếu thiết bị đầu cuối ở chế độ gói.
giật

3

Đây là những gì tôi sử dụng để in một dòng ký tự trên màn hình trong linux (dựa trên độ rộng của thiết bị đầu cuối / màn hình)

In "=" trên màn hình:

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

Giải trình:

In một dấu bằng nhiều lần như trình tự đã cho:

printf '=%.0s' #sequence

Sử dụng đầu ra của một lệnh (đây là một tính năng bash được gọi là Thay thế lệnh):

$(example_command)

Đưa ra một chuỗi, tôi đã sử dụng 1 đến 20 làm ví dụ. Trong lệnh cuối cùng, lệnh tput được sử dụng thay vì 20:

seq 1 20

Cho số lượng cột hiện đang được sử dụng trong thiết bị đầu cuối:

tput cols


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

2

Đơn giản nhất là sử dụng một lớp lót này trong csh / tcsh:

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


2

Một giải pháp thay thế thanh lịch hơn cho giải pháp Python được đề xuất có thể là:

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

1

Trong trường hợp bạn muốn lặp lại một ký tự n lần là số lần VARIABLE tùy thuộc vào độ dài của chuỗi bạn có thể làm:

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

Nó sẽ hiển thị:

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

lengthsẽ không làm việc với expr, bạn có thể có nghĩa là n=$(expr 10 - ${#vari}); tuy nhiên, sử dụng mở rộng số học của Bash đơn giản và hiệu quả hơn : n=$(( 10 - ${#vari} )). Ngoài ra, cốt lõi của câu trả lời của bạn là cách tiếp cận Perl mà OP đang tìm kiếm một giải pháp thay thế Bash .
mkuity0

1

Đây là phiên bản dài hơn của những gì Eliah Kagan đã tham gia:

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

Tất nhiên bạn cũng có thể sử dụng printf cho điều đó, nhưng không thực sự theo ý thích của tôi:

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

Phiên bản này tương thích với Dash:

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

với tôi là số ban đầu.


Trong bash và với một n: tích cực while (( i-- )); do echo -n " "; done.


1

Làm thế nào tôi có thể làm điều này với tiếng vang?

Bạn có thể làm điều này với echonếu echođược theo sau bởi sed:

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

Thật ra, điều đó echolà không cần thiết ở đó.


1

Câu trả lời của tôi phức tạp hơn một chút và có lẽ không hoàn hảo, nhưng đối với những người muốn sản xuất số lượng lớn, tôi đã có thể làm được khoảng 10 triệu trong 3 giây.

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

Đơn giản nhất là sử dụng một lớp lót này trong bash:

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


1

Một tùy chọn khác là sử dụng GNU seq và xóa tất cả các số và dòng mới mà nó tạo ra:

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

Lệnh này in #ký tự 100 lần.


1

Hầu hết các giải pháp hiện có đều phụ thuộc vào {1..10}hỗ trợ cú pháp của trình bao, cụ thể - bashzsh- cụ thể và không hoạt động trong tcshhoặc OpenBSD kshvà hầu hết không bash sh.

Các thao tác sau sẽ hoạt động trên OS X và tất cả các hệ thống * BSD trong bất kỳ hệ vỏ nào; trong thực tế, nó có thể được sử dụng để tạo ra một ma trận gồm nhiều loại không gian trang trí khác nhau:

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

Đáng buồn thay, chúng tôi không nhận được một dòng mới; có thể được sửa bởi một phụ printf '\n'sau khi gấp:

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

Người giới thiệu:


0

Đề xuất của tôi (chấp nhận giá trị biến cho n):

n=100
seq 1 $n | xargs -I {} printf =
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.