12

I am learning shell from tutorialspoint today:http://www.tutorialspoint.com/unix/unix-shell-loops.htm

And I copied this code block to a loop.sh file:

#!/bin/sh

a=0
while [ "$a" -lt 10 ]    # this is loop1
do
   b="$a"
   while [ "$b" -ge 0 ]  # this is loop2
   do
      echo -n "$b "
      b=`expr $b - 1`
   done
   echo
   a=`expr $a + 1`
done

But when excute it, I got things like:

...
-n 4
-n 3
-n 2
-n 1
-n 0
...

Apparently, the -n flag doesn't work in echo -n "$b ".

Then I move the same file to my Ubuntu virtual system. Runs it.

I got :

0
1 0
2 1 0
3 2 1 0
4 3 2 1 0
5 4 3 2 1 0
6 5 4 3 2 1 0
7 6 5 4 3 2 1 0
8 7 6 5 4 3 2 1 0
9 8 7 6 5 4 3 2 1 0

I'm confused, though Ubuntu use dash as its default sh, but OSX use official bourne shell as its sh, why the script failed here? How to fix it?

21

Echo is both a binary program (/bin/echo) as well as builtin command in some shells such as bash and sh. As man echo states,

 Some shells may provide a builtin echo command which is similar or iden-
 tical to this utility.  Most notably, the builtin echo in sh(1) does not
 accept the -n option.  Consult the builtin(1) manual page.

In order to fix this, you can change your script to use bash by setting the first line to

#!/bin/bash

Or change your invocation of echo to

/bin/echo -n "$b "
6
  • So tutorialspoint made a mistake?
    – Zen
    Feb 21 '15 at 11:38
  • 1
    Possibly but sh is not the same everywhere e.g. on OS X it is link to bash on other Unicies can be ash etc. (or even sh) and the POSIX definition of echo says -n is not portable
    – mmmmmm
    Feb 21 '15 at 13:33
  • 1
    @Zen, Tutorials.com probably assumed Linux. Mark, you are absolutely right. Since this is on Ask Different I answered for OS X. Feb 21 '15 at 13:40
  • 1, OSX (Atleast 10.10.x) Bash does not use builtin echo. 2. If bash is invoked with the name sh, it tries to mimic the startup behavior of historical versions of sh as closely as possible, while conforming to the POSIX standard as well. The Builtin echo for Bash as SH will work as SH's builtin echo should. It will act differently than the Bash builtin echo.
    – cde
    Oct 30 '15 at 7:12
  • @cde, are you sure that bash does not use the built-in echo? Invoked as /bin/bash or as /bin/sh, type echo returns echo is a shell builtin for me. Oct 30 '15 at 14:03
13

echo is not portable as you experienced. I would suggest to use the POSIX alternative printf:

printf "$b "

Note that in the general case, if you don't know in advance what contains $b, you should use instead:

printf "%s " "$b"
2
  • Just quick note that if anything in $b starts with a dash you should do printf -- "$b " otherwise it will throw an error.
    – Ulukai
    Nov 23 '20 at 10:18
  • @Ulukai That's correct but not robust enough as it would still choke if $b contains %. My suggestion was limited to the OP case.
    – jlliagre
    Nov 23 '20 at 10:51
2

Appending '\c' as per "man echo" documentation seemed to do this for me. So ... on line 9 the change would like below

#!/bin/sh

a=0
while [ "$a" -lt 10 ]    # this is loop1
do
   b="$a"
   while [ "$b" -ge 0 ]  # this is loop2
   do
      echo "$b \c"
      b=`expr $b - 1`
   done
   echo
   a=`expr $a + 1`
done
1
  • This'll work for some versions of echo (under some conditions), but in other cases it might just print "\c" as part of the output (unless you also supply the -e option, but then some versions will print that...). Overall, if you value your sanity, use printf instead of echo for any situation where you need to skip the newline and/or process escape sequences. Sep 30 '19 at 23:12
1

The builtin echo for /bin/sh on macOS does not support the -n option. You can use the following alternatives:

bash

Using /bin/bash works. But I don't like using it for scripts because it is not available by default for example on FreeBSD. On Linux it is available.

builtin echo and tr

If you want to use the builtin echo (e.g. when you are echoing a password and do not want to appear in ps) and want to have it more portable, you can use:

echo hello | tr -d '\n'

printf or /bin/echo

Otherwise printf or /bin/echo -n are good options, too.

Just be aware that printf takes a formatted string, so if you are including a variable in the string that might include a % or \ character, it might break things. For example:

$ w=%world ; printf "hello $w"
printf: %w: invalid directive
hello %

In such case you can use %s as format string.

$ w=%world ; printf %s "hello $w"
hello %world

\c suffix

Another option is appending \c to the end of the string when using echo (see man echo on macOS). It works fine on macOS but it's not very portable.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .