Як я можу повторити персонаж у Bash?


240

Як я міг це зробити echo?

perl -E 'say "=" x 100'

На жаль це не Баш.
солідний хід

1
не з відлунням, але на ту саму тему ruby -e 'puts "=" * 100'абоpython -c 'print "=" * 100'
Євгеній

1
Чудове запитання. Дуже хороші відповіді. Тут я використав одну з відповідей у ​​реальній роботі, яку опублікую як приклад: github.com/drbeco/oldfiles/blob/master/oldfiles (використовується printfдля seq)svrb=`printf '%.sv' $(seq $vrb)`
д-р Беко

Загальне рішення для друку будь-якого (1 або більше символів, навіть включаючи нові рядки): Repeat_this () {i = 1; while ["$ i" -le "$ 2"]; зробити printf "% s" "$ 1"; i = $ (($ i + 1)); зроблено; printf '\ n';}. Використовуйте так: Повторіть це "щось" число_обов'язків. Наприклад, продемонструвати повторення 5 разів чогось, включаючи 3 нові рядки: Повторіть_це це "$ (printf '\ n \ n \ n \ nце") "5. Остаточний printf '\ n' може бути виведений (але я вкладаю його, щоб створити текстові файли, і їм потрібен новий рядок як їх останній символ!)
Олів'є Дулак,

Відповіді:


396

Ви можете використовувати:

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

Як це працює:

Bash розширюється {1..100}, так що команда стає:

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

Я встановив формат printf, =%.0sщо означає, що він завжди буде друкувати єдиний =незалежно від того, який аргумент він наводиться. Тому він друкує 100 =с.


14
Чудове рішення, яке працює досить добре навіть при великих підрахунках. Ось функціональна оболонка, з якою можна запустити repl = 100, наприклад ( evalхитрість потрібна, на жаль, для основи розширення repl() { printf "$1"'%.s' $(eval "echo {1.."$(($2))"}"); }
дужок

7
Чи можна встановити верхню межу за допомогою var? Я спробував і не можу змусити його працювати.
Майк Перселл

70
Ви не можете використовувати змінні в межах розширення дужок. Використовуйте seqзамість цього , наприклад $(seq 1 $limit).
dogbane

11
Якщо ви це функціоналізуєте, то краще переставити його, $s%.0sщоб %.0s$sінакше тире спричинило printfпомилку.
KomodoDave

5
Це змусило мене помітити поведінку Баша printf: він продовжує застосовувати рядок формату, поки не залишиться аргументів. Я припускав, що обробляє рядок формату лише один раз!
Йену

89

Непростий спосіб. Але, наприклад:

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

А може бути стандартним способом:

printf %100s |tr " " "="

Також є tput rep, але що стосується моїх терміналів під рукою (xterm та linux), схоже, вони не підтримують це :)


3
Зауважте, що перший параметр із послідовністю друкує на один менший від заданого числа, тому цей приклад надрукує 99 =символів.
Каміло Мартін

13
printf trє єдиним рішенням , так як POSIX seq, yesа {1..3}НЕ POSIX.
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

2
Щоб повторити рядок, а не лише один символ: printf %100s | sed 's/ /abc/g'- виводить 'abcabcabc ...'
Джон Рікс,

3
+1 за використання циклів і лише одну зовнішню команду ( tr). Ви також можете поширити це на щось подібне printf "%${COLUMNS}s\n" | tr " " "=".
musiphil

2
@ mklement0 Ну, я сподівався, що помилково порахували останній новий рядок wc. Єдиний висновок, який я можу зробити з цього, - це " seqне слід використовувати".
Каміло Мартін

51

Підказка капелюха до @ gniourf_gniourf для його введення.

Примітка. Ця відповідь не відповідає на початкове запитання, але доповнює існуючі корисні відповіді, порівнюючи ефективність .

Рішення порівнюються лише з точки зору швидкості виконання - вимоги до пам’яті не враховуються (вони різняться в різних рішеннях і можуть мати значення при великій кількості повторень).

Підсумок:

  • Якщо кількість повторень невелика , скажімо, приблизно до 100, варто скористатися рішеннями , що стосуються лише Bash , оскільки важлива вартість запуску зовнішніх комунальних послуг, особливо Perl.
    • Прагматично кажучи, якщо вам потрібен лише один екземпляр повторюваних символів, всі існуючі рішення можуть бути нормальними.
  • При великих підрахунках повторних , використовувати зовнішні утиліти , так як вони будуть набагато швидше.
    • Зокрема, уникайте заміни глобальної підрядки Баша великими рядками
      (наприклад, ${var// /=}), оскільки це дуже повільно.

Нижче наведено таймінги на iMac наприкінці 2012 року з процесорним процесором Intel Core i5 3,2 ГГц та Fusion Drive, що працює під керуванням OSX 10.10.4 та bash 3.2.57.

Записи:

  • вказаний у порядку зростання тривалості виконання (перший найшвидший)
  • з приставкою:
    • M... потенційно мульти -character рішення
    • S... єдине рішення
    • P ... сумісне з POSIX рішення
  • з подальшим коротким описом рішення
  • суфікс із прізвищем автора вихідної відповіді

  • Невелика кількість повторів: 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
  • Рішення, що стосуються лише Bash, очолюють пакет, але лише з повторним підрахунком цього невеликого! (Дивись нижче).
  • Тут важлива стартова вартість зовнішніх утиліт, особливо Perl. Якщо ви повинні викликати це в циклі - з невеликими підрахунками повторень у кожній ітерації - уникайте мультикорисності awkта perlрішень.

  • Велика кількість повторень: 1000000 (1 мільйон)
[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
  • Рішення Perl з питання, безумовно, найшвидше.
  • Глобальна заміна рядків ( ${foo// /=}) Bash незбагненно повільна з великими рядками, і вона була виведена з роботи (зайняла близько 50 хвилин (!) В Bash 4.3.30, і ще довше в Bash 3.2.57 - я ніколи не чекав на це закінчити).
  • Петлі Bash повільні, а арифметичні петлі ( (( i= 0; ... ))) повільніші, ніж розширені дужки ( {1..n}), хоча арифметичні петлі є більш ефективними в пам'яті.
  • awkвідноситься до BSD awk (як це також зустрічається на OSX) - він помітно повільніше, ніж gawk(GNU Awk) і особливо mawk.
  • Зауважте, що з великим рахунком і багатозначністю. Струни, споживання пам’яті можуть стати увагою - підходи в цьому відношенні відрізняються.

Ось сценарій Bash ( testrepeat), який створив вище. Потрібно 2 аргументи:

  • кількість повторень символів
  • необов'язково, кількість тестів для виконання та обчислення середнього часу з

Іншими словами: вищезазначені терміни були отримані з testrepeat 100 1000іtestrepeat 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

Цікаво порівняти часові порівняння, але я думаю, що у багатьох програмах вихід буферний, тому їх час можна змінити, якщо буферизацію було вимкнено.
Сергій Колодяжний

In order to use brace expansion with a variable, we must use `eval`👍
піб

2
Таким чином, рішення perl (sid_com), в основному, є найшвидшим ... як тільки буде досягнуто початкового накладних витрат на запуск perl. (Він йде від 59ms для невеликого повторення до 67ms за мільйон повторів ... тому Perl розгалуження взяло близько 59ms на вашій системі)
Олів'є Дюлак

46

Існує більше ніж один спосіб зробити це.

Використання циклу:

  • Розширення дужок можна використовувати з цілими буквами:

    for i in {1..100}; do echo -n =; done    
  • С-подібний цикл дозволяє використовувати змінні:

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

Використання printfвбудованого:

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

Вказавши точність, тут обрізається рядок відповідно до заданої ширини ( 0). Оскільки printfповторно використовується рядок формату для споживання всіх аргументів, він просто друкується "="100 разів.

Використання head( printfтощо) та tr:

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

2
++ для head/ trрішення, яке добре працює навіть при високих підрахунках повторень (невеликий застереження: head -cне сумісний з POSIX, але BSD та GNU headреалізують це); в той час як інші два рішення будуть повільними в цьому випадку, вони також мають перевагу в роботі з багатознаковими рядками.
mklement0

1
Використання yesі head- корисно , якщо ви хочете певну кількість нових рядків: yes "" | head -n 100. trможе змусити його друкувати будь-який символ:yes "" | head -n 100 | tr "\n" "="; echo
loxaxs

Дещо дивно: dd if=/dev/zero count=1 bs=100000000 | tr '\0' '=' >/dev/nullзначно повільніше, ніж head -c100000000 < /dev/zero | tr '\0' '=' >/dev/nullверсія. Звичайно, вам потрібно використовувати розмір блоку 100 М +, щоб обґрунтовано виміряти різницю в часі. 100M байтів займають 1,7 с та 1 с із показаними двома відповідними версіями. Я зняв tr і просто скинув його /dev/nullі отримав 0,287 s для headверсії та 0,675 s за ddверсію на мільярд байтів.
Майкл Голдштейн

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

31

Щойно я знайшов серйозно простий спосіб зробити це за допомогою seq:

ОНОВЛЕННЯ: Це працює на BSD, seqякий постачається з OS X. YMMV з іншими версіями

seq  -f "#" -s '' 10

Буде надруковано "#" 10 разів, як це:

##########
  • -f "#"встановлює форматний рядок ігнорувати числа та просто друкувати #для кожного.
  • -s '' встановлює роздільник на порожній рядок, щоб видалити нові рядки, які послідовно вставляє між кожним числом
  • Пробіли після -fі -sздаються важливими.

EDIT: Ось це зручна функція ...

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

Якого ви можете назвати так ...

repeat "#" 10

ПРИМІТКА: Якщо ви повторюєте, #то цитати важливі!


7
Це дає мені seq: format ‘#’ has no % directive. seqє для чисел, а не для рядків. Дивіться gnu.org/software/coreutils/manual/html_node/seq-invocation.html
Іван Б

Ах, значить, я використовував BSD-версію послідовності, знайденої в OS X. Я оновлю відповідь. Яку версію ви використовуєте?
Сем Солсбері

Я використовую seq з GNU coreutils.
Джон Б

1
@JohnB: BSD seqйде розумно переорієнтований тут дублюючі рядки : рядок формату передається -f- зазвичай використовується для форматування числа генерується - містить тільки рядок повторити тут , так що висновок містить копії тільки той рядок. На жаль, GNU seqнаполягає на присутності формату чисел в рядку формату, який є помилкою , яку ви бачите.
mklement0

1
Чудово зроблено; також працює з декількома рядками. Будь ласка, використовуйте "$1"(подвійні лапки), щоб ви також могли передавати такі символи, як '*'рядки та вбудовані пробіли. Нарешті, якщо ви хочете мати можливість користуватися %, вам доведеться подвоїти його (інакше seqви вважаєте, що це частина специфікації формату, наприклад %f); використовуючи "${1//%/%%}"б подбати про це. Оскільки (як ви вже згадували) ви використовуєте BSD seq , це працюватиме на BSD-подібних ОС загалом (наприклад, FreeBSD) - навпаки, це не буде працювати в Linux , де використовується GNU seq .
mklement0

18

Ось два цікавих способи:

ubuntu @ ubuntu: ~ $ yes = | голова -10 | вставити -s -d '' -
==========
ubuntu @ ubuntu: ~ $ yes = | голова -10 | tr -d "\ n"
========== ubuntu @ ubuntu: ~ $ 

Зверніть увагу, що ці два тонко різні - pasteметод закінчується в новому рядку. trМетод не робить.


1
Чудово зроблено; зауважте, що BSD paste необґрунтовно вимагає -d '\0'ввести порожній роздільник, а не вдається -d ''- -d '\0'повинен працювати з усіма POSIX-сумісними pasteреалізаціями і справді працює і з GNU paste .
mklement0

Схожий за духом, з меншою кількістю підвісних інструментів:yes | mapfile -n 100 -C 'printf = \#' -c 1
єпископ

@bishop: Хоча ваша команда справді створює меншу кількість підзаголовків, для більш високих підрахунків повторів вона все-таки повільніше, а для менших підрахунків повторів різниця, ймовірно, не має значення; точний поріг, ймовірно, залежить як від апаратури, так і від ОС, наприклад, на моїй машині OSX 10.11.5 ця відповідь вже швидша на 500; спробуйте time yes = | head -500 | paste -s -d '\0' -; time yes | mapfile -n 500 -C 'printf = \#' -c 1. Що ще важливіше: якщо ви все- printfтаки використовуєте , ви можете також скористатися більш простим та ефективним підходом із прийнятої відповіді:printf '%.s=' $(seq 500)
mklement0

13

Немає простого способу. Уникайте циклів використання printfта заміни.

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

2
Приємно, але виконує лише розумно з невеликими повторними підрахунками. Ось функція обгортка, яку можна викликати repl = 100, наприклад, (не видає трейлінг \n):repl() { local ts=$(printf "%${2}s"); printf %s "${ts// /$1}"; }
mklement0

1
@ mklement0 Приємно, що ви надали версії функцій обох рішень, +1 для обох!
Каміло Мартін

8

Якщо ви хочете POSIX-відповідності та узгодженості між різними реалізаціями echoі printf, і / або оболонок , ніж тільки 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'скрізь.


1
Проблема полягає в тому, що seqце не утиліта POSIX (хоча системи BSD та Linux мають її реалізацію) - ви можете робити арифметику оболонки POSIX whileзамість циклу, як у відповіді @ Xennex81 (з printf "=", як ви правильно вважаєте, а не echo -n).
mklement0

1
На жаль, ви абсолютно праві. Такі речі просто пропускають повз мене іноді, оскільки цей стандарт не має ніякого сенсу. calє POSIX. seqне. У будь-якому разі, замість того, щоб переписувати відповідь циклом на час (як ви говорите, це вже є в інших відповідях), я додам функцію RYO. Більш освічений таким чином ;-).
Джефф Ніксон

8

Питання полягало у тому, як це зробити echo:

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

Це зробить точно так само, як perl -E 'say "=" x 100'тільки з echoлише.


Тепер це незвично, якщо ви не змішуєте в ньому зайві простори .. або очищаєте його за допомогою: echo -e $ _ {1..100} '\ b =' | кол
Антоні

1
Погана ідея. Це не спрацює , якщо $_1, $_2або будь-який інший з сотень змінних мають значення.
Джон Кугельман

@JohnKugelman echo $ (встановити -; eval echo -e \ $ {{1..100}} '\\ b =')
mug896

Це валово . Мені подобається: D
dimo414

6

Чистий спосіб Bash з відсутністю eval, без допоміжних оболонок, без зовнішніх інструментів, без розширень дужок (тобто ви можете мати число для повторення в змінній):

Якщо вам надана змінна, nяка розширюється на (негативне) число та змінну pattern, наприклад,

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

Ви можете зробити функцію за допомогою цього:

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

За допомогою цього набору:

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

Для цього маленького трюку ми printfдосить багато використовуємо:

  • -v varname: замість друку до стандартного виводу, printfвміст відформатованого рядка буде розміщено змінною varname.
  • '% * s': printfбуде використовувати аргумент для друку відповідної кількості пробілів. Наприклад, printf '%*s' 42буде надруковано 42 пробіли.
  • Нарешті, коли у нас є змінна кількість пробілів у нашій змінній, ми використовуємо розширення параметрів, щоб замінити всі пробіли нашим шаблоном: ${var// /$pattern}розшириться до розширення varз усіма пробілами, заміненими розширенням $pattern.

Ви також можете позбутися tmpзмінної 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}"
}

Цікава варіація для передачі імені змінної. Хоча це рішення чудово для повторних підрахунків до приблизно 1000 (і, таким чином, ймовірно, добре для більшості програм реального життя, якщо я здогадуюсь), воно стає дуже повільним для більш високих підрахунків (див. Далі коментар).
mklement0

Здається, що bashоперації з заміни глобальних рядків у контексті розширення параметрів ( ${var//old/new}) особливо повільні: болісно повільні в bash 3.2.57і повільні в bash 4.3.30, принаймні на моїй системі OSX 10.10.3 на машині Intel Core i5 3,2 ГГц: кількість 1 000, все відбувається повільно ( 3.2.57) / швидко ( 4.3.30): 0,1 / 0,004 секунди. Зростання кількості до 10000 приносить вражаюче різні цифри: repeat 10000 = varзаймає приблизно 80 секунд (!) В баші 3.2.57і приблизно 0,3 секунди в баші 4.3.30(набагато швидше, ніж на 3.2.57, але все ж повільно).
mklement0

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

Або

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

Приклад


3
Чудово зроблено; це сумісно з POSIX і досить швидко навіть при високих підрахунках повторень, а також підтримує багато символьні рядки. Ось версія оболонки: awk 'BEGIN { while (c++ < 100) printf "=" }'. Загорнута в параметризрвані функції оболонки (Invoke , як repeat 100 =, наприклад): repeat() { awk -v count="$1" -v txt=".$2" 'BEGIN { txt=substr(txt, 2); while (i++ < count) printf txt }'; }. ( .substrawk=
Офіційна таблиця

1
NF = 100Рішення дуже розумний (хоча , щоб отримати 100 =, ви повинні використовувати NF = 101). Застережень є те, що він виходить з ладу BSD awk(але це дуже швидко з gawkі навіть швидше mawk), і що POSIX обговорюється ні не призначили до NF, ні використання полів у BEGINблоках. Ви можете змусити його працювати і в BSD awk, маючи невелику настройку: awk 'BEGIN { OFS = "="; $101=""; print }'(але цікаво, що в BSD awkце не швидше рішення циклу). В якості вирішення параметризрвані оболонки: repeat() { awk -v count="$1" -v txt=".$2" 'BEGIN { OFS=substr(txt, 2); $(count+1)=""; print }'; }.
mklement0

Примітка для користувачів - фокус NF = 100 спричиняє помилку сегменту на старій стадії. Це original-awkім'я під Linux старішого awk, схожого на awk BSD, яке також було повідомлено про збій, якщо ви хочете спробувати це. Зауважте, що збої - це зазвичай перший крок до пошуку помилкової помилки. Ця відповідь настільки пропагує небезпечний код.

2
Примітка для користувачів - original-awkнестандартна і не рекомендується
Стівен Пенні

Альтернативою першому фрагменту коду може бути awk NF=100 OFS='=' <<< ""(з використанням bashі gawk)
oliv

4

Я здогадуюсь, що початковою метою цього питання було зробити це лише за допомогою вбудованих команд оболонки. Так forпетля і printfs будуть законними, в той час як rep, perlі також jotнижче не буде. Все-таки наступна команда

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

наприклад, друкує вікно по всій лінії вікна \/\/\/\/\/\/\/\/\/\/\/\/


2
Чудово зроблено; це добре працює навіть при високій кількості повторень (одночасно підтримуючи рядки з декількома символами). Для того, щоб краще проілюструвати підхід, тут еквівалент команди OP в: jot -s '' -b '=' 100. Проблема полягає в тому , що в той час як BSD-подібних платформах, включаючи OSX, приходять з jot, дистрибутиви Linux не .
mklement0

1
Дякую, мені подобається, що ти використовуєш -s '' ще краще. Я змінив свої сценарії.
Стефан Людвіг

На останніх системах, що базуються на Debian , apt install athena-jotбуло б передбачено jot.
АРУ

4

Як вже говорили інші, розширення в bash brace передує розширенню параметрів , тому діапазони можуть містити лише літерали. і забезпечити чисті рішення, але вони не є повністю портативними з однієї системи в іншу, навіть якщо ви використовуєте однакову оболонку для кожної. (Хоча це все більше доступно; наприклад, у FreeBSD 9.3 і вище .) Та інші форми непрямості завжди працюють, але дещо неелегантні.{m,n}seqjotseqeval

На щастя, bash підтримує C-стиль для циклів (лише з арифметичними виразами). Тож ось стислий "чистий баш" спосіб:

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

Це сприймає кількість повторів як перший аргумент, а рядок, яку потрібно повторити (що може бути одним символом, як в описі проблеми), як другий аргумент. repecho 7 bвиводи bbbbbbb(закінчується новим рядком).

Денніс Вільямсон дав по суті це рішення чотири роки тому в його прекрасному відповідь на створення рядка символів, що повторюються в сценарії оболонки . Моє функціональне тіло дещо відрізняється від коду, який там є:

  • Оскільки в центрі уваги є повторення одного символу, а оболонка - це удар, його, мабуть, безпечно використовувати echoзамість printf. І я читаю опис проблеми в цьому питанні як вираження переваги для друку echo. Вищеописане визначення функції працює у bash та ksh93 . Хоча він printfє більш портативним (і зазвичай його слід використовувати для подібних речей), echoсинтаксис, можливо, є більш читабельним.

    Деякі echoвбудовані оболонки інтерпретують -самі собою як варіант - навіть незважаючи на те, що звичайний сенс -використовувати stdin для введення даних, є безглуздим echo. zsh робить це. І напевно існують такі echo, які не розпізнають -n, як це не стандартно . (Багато оболонок у стилі Борна взагалі не приймають шлейф у стилі С, тому їх echoповедінку не слід враховувати.)

  • Тут завдання - надрукувати послідовність; там , це було призначити його змінної.

Якщо $nпотрібна кількість повторень, і вам не доведеться використовувати її повторно, і вам потрібно щось ще коротше:

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

nповинна бути змінною - цей спосіб не працює з позиційними параметрами. $sце текст, який потрібно повторити.


2
Настійно уникайте робити циклічні версії. printf "%100s" | tr ' ' '='є оптимальним.
ocodo

Хороша довідкова інформація та кудо для упаковки функціональності як функції, яка працює zshі до речі. Підхід "Ехо-в-циклі" добре працює для менших підрахунків повторів, але для великих є альтернативи, сумісні з POSIX, засновані на утилітах , про що свідчить коментар @ Slomojo.
mklement0

Додавання круглих дужок навколо коротшого циклу зберігає значення n, не впливаючи на (while ((n--)); do echo -n "$s"; done; echo)

використовуйте printf замість відлуння! це набагато портативніше (echo -n може працювати лише в деяких системах). дивіться unix.stackexchange.com/questions/65803/… (одна з приголомшливих відповідей Стефана Шазеласа)
Олів'є Дулак

@OlivierDulac Тут йдеться про баш. Незалежно від того, в якій операційній системі ви працюєте, якщо ви використовуєте bash на ній , bash має echoвбудований модуль, який підтримує -n. Дух того, що ви говорите, абсолютно правильний. printfмайже завжди слід віддавати перевагу echo, принаймні, у неінтерактивному використанні. Але я не вважаю, що якимось чином недоречним чи оманливим було давати echoвідповідь на запитання, яке задало питання, і яке дало достатньо інформації, щоб знати, що це буде працювати . Зауважте також, що POSIX не гарантує підтримку ((n--))(без $).
Елія Каган

4

Python є всюдисущим і працює скрізь однаково.

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

Символ та кількість передаються як окремі параметри.


Я думаю, що це був намір тутpython -c "import sys; print(sys.argv[1] * int(sys.argv[2]))" "=" 100
гажай

@loevborg - це не трошки надумано?
Sapphire_Brick


3

Ще одне значення повторити довільну рядок n разів:

Плюси:

  • Працює з оболонкою POSIX.
  • Вихідні дані можуть бути призначені змінній.
  • Повторює будь-який рядок.
  • Дуже швидко навіть при дуже великих повторах.

Мінуси:

  • Потрібна yesкоманда Gnu Core Utils .
#!/usr/bin/sh
to_repeat='='
repeat_count=80
yes "$to_repeat" | tr -d '\n' | head -c "$repeat_count"

З терміналом ANSI та символами US-ASCII для повторення. Можна використовувати послідовність аварійної послідовності ANSI CSI. Це найшвидший спосіб повторити персонаж.

#!/usr/bin/env bash

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

Або статично:

Роздрукуйте рядок у 80 разів =:

printf '=\e[80b\n'

Обмеження:

  • Не всі термінали розуміють repeat_charпослідовність ANI CSI.
  • Можуть повторюватися лише US-ASCII або однобайтові символи ISO.
  • Повторення зупиняється на останньому стовпці, тому ви можете використовувати велике значення для заповнення цілого рядка незалежно від ширини терміналу.
  • Повтор лише для показу. Захоплення виводу в змінну оболонки не розширить repeat_charпослідовність ANSI CSI на повторний символ.

1
Незначна примітка - REP (CSI b) повинен нормально завертатися, якщо термінал перебуває в режимі обгортання.
ривок

3

Ось що я використовую для друку рядків символів на екрані в Linux (на основі ширини терміналу / екрана)

Друк "=" на екрані:

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

Пояснення:

Надрукуйте знак рівності стільки разів, скільки заданої послідовності:

printf '=%.0s' #sequence

Використовуйте вихід команди (це функція bash під назвою Command Substitution):

$(example_command)

Наведіть послідовність, я використав від 1 до 20 як приклад. У заключній команді використовується команда tput замість 20:

seq 1 20

Укажіть кількість стовпців, які зараз використовуються в терміналі:

tput cols


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

2

Найпростішим є використання цього одного вкладиша в csh / tcsh:

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


2

Більш елегантною альтернативою запропонованому рішенням Python може бути:

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

1

У випадку, якщо ви хочете повторити символ n разів, який знаходиться на VARIABLE кількість разів залежно від, скажімо, довжини рядка, який ви можете виконати:

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

На ньому відображаються:

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

lengthне буде працювати expr, ви, мабуть, мали на увазі n=$(expr 10 - ${#vari}); Однак, це простіше і ефективніше використовувати арифметичне розширення в Bash: n=$(( 10 - ${#vari} )). Крім того, в основі вашої відповіді лежить той самий підхід Perl, який ОП шукає альтернативу Баша .
mklement0

1

Це довша версія того, що підтримував Елія Каган:

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

Звичайно, для цього можна використовувати і printf, але не дуже мені подобається:

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

Ця версія сумісна з тире:

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

при цьому я є початковим числом.


В баш і з позитивним н: while (( i-- )); do echo -n " "; doneпрацює.

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
}

Проба працює

$ repeatString 'a1' 10 
a1a1a1a1a1a1a1a1a1a1

$ repeatString 'a1' 0 

$ repeatString '' 10 

Довідкова інформація на: https://github.com/gdbtek/linux-cookbooks/blob/master/libraries/util.bash


1

Як я міг це зробити із відлунням?

Ви можете зробити це за echoдопомогою echoнаступного sed:

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

Насправді, echoце там непотрібно.


1

Моя відповідь трохи складніша і, мабуть, не ідеальна, але для тих, хто прагне вивести велику кількість, я зміг зробити близько 10 мільйонів за 3 секунди.

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

Найпростішим є використання цього однолінійного вкладиша в bash:

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


1

Іншим варіантом є використання послідовності GNU та видалення всіх чисел та нових рядків, які він створює:

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

Ця команда друкує #персонажа 100 разів.


1

Більшість існуючих рішень залежать від {1..10}підтримки синтаксису оболонки, яка є bash- і zsh- специфічною, і не працює в tcshабо OpenBSD kshта в більшості, що не є bash sh.

У ОС X і всіх * BSD-системах у будь-якій оболонці має працювати наступне; насправді він може бути використаний для створення цілої матриці різних видів декоративного простору:

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

На жаль, ми не отримуємо зворотного нового рядка; які можна зафіксувати додатково printf '\n'після складки:

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

Список літератури:


Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.