15

How can I repeat the character - n times in shell? I have read and tried this, but this does not work for -. It throws error invalid option. Below is the exact command that I used:

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

Original posted line: printf '-%0.s' {1..100}

I also tried escaping - by putting a \ but in that case it repeats \- n times.

Community
  • 1
  • 1
Sonu Mishra
  • 1,659
  • 4
  • 26
  • 45
  • The title says "dash", but it's tagged with "bash". Which one do you mean? – Kusalananda Aug 10 '16 at 12:00
  • @Kusalananda: The OP refers to the dash _character_ (`-`, ASCII / Unicode code point `0x2d`). (Strictly speaking, the character's name is _hyphen_, but in practice the two names are used interchangeably.) – mklement0 Aug 10 '16 at 12:18
  • 1
    @mklement0 Ah. Severe misunderstanding on my part. I've read too many question with badly mangled language lately... – Kusalananda Aug 10 '16 at 12:19

7 Answers7

18

This throws an error:

$ printf '-%.0s' {1..100}; echo ""
bash: printf: -%: invalid option
printf: usage: printf [-v var] format [arguments]

This works fine under bash:

$ printf -- '-%.0s' {1..100}; echo ""
----------------------------------------------------------------------------------------------------

For other shells, try:

printf -- '-%.0s' $(seq 100); echo ""

The problem was the printf expects that - starts an option. As is common among Unix/POSIX utilities in this type of situation, -- signals to printf to expect no more options.

John1024
  • 109,961
  • 14
  • 137
  • 171
  • 3
    +1. And this solution is good to remember, because it's not specific to printf; many utilities support `--` as a special flag meaning "no more options after this point". – ruakh Aug 09 '16 at 23:26
  • 2
    Not just Linux utilities, it's a POSIX-specified utility convention. See Guideline 10 in http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap12.html – Charles Duffy Aug 09 '16 at 23:27
  • @CharlesDuffy Very good. Answer updated to mention POSIX. I checked [POSIX `printf`](http://pubs.opengroup.org/onlinepubs/9699919799/utilities/printf.html) and, since it accepts no options, it makes no mention of accepting `--`. – John1024 Aug 09 '16 at 23:36
  • 1
    Indeed. That said, the utility-guidelines section of POSIX indicates that deviations from the guidelines will be described in the OPTIONS section of a utility's individual documentation. – Charles Duffy Aug 09 '16 at 23:40
7

John1024's helpful answer provides a generic solution that shows how to disambiguate options from operands for all POSIX-like utilities.

In the case at hand, the simplest solution is (works not only in bash, but also in ksh and zsh):

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

Placing %.0s before the - avoids the issue of an initial - getting mistaken for an option.

Slightly optimized:[1]

printf '%.s-' {1..100}

[1] %.0s is in practice the most portable form (to be fully portable, you must also avoid the brace expansion, {...}).
%.s, the equivalent shorthand form, is supported by bash, ksh, and dash, but not zsh <= v5.2 - even though it is equally POSIX-compliant : "The precision [the part after .] shall take the form of a ( '.' ) followed by a decimal digit string; a null digit string is treated as zero."

As a side note: The question originally contained a benign (in Bash) typo that sparked a debate: %0.s instead of %.0s: %0.s should effectively be the same as %.0s, and for that matter, the same as %.s and %0.0s (all effectively request: print a [minimum zero-width] field filled with a zero-length string), but in practice isn't: zsh <= v5.2 doesn't handle %0.s correctly (again, due to the .s part).
Similarly, the GNU printf external-utility implementation (/usr/bin/printf), as of GNU coreutils v8.24, reports an error with %0.s, because it generally doesn't accept a field width of 0 with s: invalid conversion specification - this matters for lesser-known shells that don't provide printf as a builtin. Note that the BSD/OSX implementation does not have this problem.
Both zsh's (<= v5.2) behavior with %.s and GNU /usr/bin/printf's behavior with %0s are deviations from the POSIX spec that smell like bugs.
This question asks about zsh's behavior regarding %.s, and the bug has since been confirmed and reported as fixed via a post-v5.2 commit that has yet to be released as of this writing.

Community
  • 1
  • 1
mklement0
  • 382,024
  • 64
  • 607
  • 775
  • If, as you comment, zsh and GNU printf (at least) fail (at the time you are writing this) with `%0.s` and/or `%.s` then both options are not portable and you should (if you act in a responsible way) not recommend them. –  Aug 12 '16 at 03:30
  • @BinaryZebra: There is no such recommendation in my answer. There _is_ a recommendation to use `%.0s` as the currently most portable form, _if_ portability is needed (incidental to this question, because, as stated, _all_ format-string variations work fine in `bash`). Beyond that, there is an analysis of differing behavior in real-world implementations and how they relate to the POSIX spec. – mklement0 Aug 12 '16 at 03:50
  • @mklement0 The statement `%0.s should effectively be the same as %.0s` is false. Your initial mistake (check edit 1) was to propose that `%0.s` was the solution. You have never said that that was wrong. Do it now. –  Aug 12 '16 at 06:58
  • @BinaryZebra: Saying that the statement is false without explaining _why_ is unhelpful. My answer _explains_ why they should be the same, so if you find fault in that reasoning, do tell us. `%0.s` works just fine in `bash`, `ksh`, and `dash`, due to the explained equivalence; it doesn't work in `zsh` <= 5.2 and GNU `/usr/bin/printf` due to (unrelated) bugs. However, because `%0.s` is not a good expression of the solution's intent (and started out as a typo by the OP, long since corrected) and _practically_ limits portability, I switched to `%.0s` (and only discuss `%0.s` in the _footnote_). – mklement0 Aug 12 '16 at 07:21
1

Use a for loop and number range:

for i in {1..10}; 
    do echo "-"; 
done

Or on a single line:

for i in {1..10}; 
    do echo -n "-"; 
done

which outputs ----------.

EDIT: This was before your printf edit.

Nick Bull
  • 9,518
  • 6
  • 36
  • 58
  • 2
    `echo -n` is explicitly non-POSIX-specified behavior. You might stay clear of it if trying to be portable. See http://pubs.opengroup.org/onlinepubs/009604599/utilities/echo.html: "If the first operand is -n, or if any of the operands contain a backslash ( '\' ) character, the results are implementation-defined" – Charles Duffy Aug 09 '16 at 23:27
  • Thanks for the tip! Is `printf` a better alternative for that reason? – Nick Bull Aug 09 '16 at 23:28
  • 2
    Yes. (See the APPLICATION USAGE section of the prior link). – Charles Duffy Aug 09 '16 at 23:28
1

I would recommend using a traditional for loop, as there is no need to spawn sub-processes or expand 100 arguments:

N=100
for((i = 0; i < $N; ++i)); do
  printf -
done

It is curious that printf -%s triggers "invalid option" but printf - does not. To perhaps be extra safe, you could do printf %s -.

mooiamaduck
  • 2,066
  • 12
  • 13
  • 2
    Not really that curious. `-` is a completely valid argument, conventionally used to refer to stdin or stdout as appropriate to context, whereas `-x` sets an option flag. – Charles Duffy Aug 09 '16 at 23:33
  • 1
    No need for a `$` inside an arithmetic evaluation: `for((i = 0; i –  Aug 10 '16 at 00:30
  • 2
    `printf '%.s-' {1..100}` involves _no_ subshells, because brace expansion is a shell feature, and `printf` is a Bash _builtin_. As such, combining the brace expansion with builtin `printf` is _not_ subject to the max. command-line length, but you're right that with large repeat counts there are alternatives that are both faster and especially more memory-efficient (but a _shell loop_ is _not_ the right tool for large repeat counts) - see [the answer the OP linked to](http://stackoverflow.com/questions/5349718/how-can-i-repeat-a-character-in-bash). – mklement0 Aug 10 '16 at 03:24
  • 1
    @mklement0 Indeed, I was thinking of the solutions involving `perl`, `tr`, `head`, etc. with regard to the sub-process criticism. If `printf` were not a built-in, then the solution I posted would spawn 100 processes! And yes, my criticism with using `{1..N}` is that it requires O(N) space. – mooiamaduck Aug 10 '16 at 14:43
  • Got it - makes perfect sense. `/usr/bin/printf '-%.0s' {1..100}` still only involves 1 child process (to which a potentially very long list of arguments is passed), but with an external utility you do have to worry about the max. command-line length (as reported by `getconf ARG_MAX`). – mklement0 Aug 10 '16 at 15:08
  • 1
    In fact, `{1..N}` actually requires Ω(N log N) space because the size of each argument increases logarithmically... – mooiamaduck Aug 10 '16 at 15:17
1

You can also use tput along with printf to accomplish filling the terminal with an exact number of dashes, either drawing the line directly, or writing the line of dashes to a variable (say line) for repeated use, e.g. to write a screen width line of dashes to the variable line, you could do:

$ eval printf -v line '%.0s-' {1..$(tput cols)}

and then simply echo "$line" each time you need to draw the line. You can also write it directly to the screen with.

$ eval printf '%.0s-' {1..$(tput cols)}

(I'm not a huge fan of eval, but this is one use where you are guaranteed the result of the command cannot be harmful).

David C. Rankin
  • 81,885
  • 6
  • 58
  • 85
1

This has been my goto for years... not AIX friendly though. Change the last character to change the repeating character which makes up the line

printf '%*s\n' "${COLUMNS:-$(tput cols)}" '' | tr ' ' _
SeanO
  • 111
  • 2
0
  1. jot can do it, with no bashisms:

    jot -s '' -b - 100
    
  2. seq too, but not as well, it needs a tr:

    seq -s- 100 | tr -d '[0-9]'
    
agc
  • 7,973
  • 2
  • 29
  • 50
  • 1
    Given as `jot` is not a POSIX-specified tool, it's arguably every bit as nonportable as, well, bash. Exactly the same criticism goes for `seq`. – Charles Duffy Aug 11 '16 at 03:46
  • @CharlesDuffy, true... but there's no *POSIX* tag in the OP. – agc Aug 11 '16 at 03:50