Bash parameter expansion works only in interactive shell, but not in script - bash

In user's console I have bash:
$ echo $SHELL
/bin/bash
$ bash --version
GNU bash, version 4.2.46(1)-release (x86_64-redhat-linux-gnu)
I have code in file test.sh:
$ cat test.sh
aaa='---aa-aa---'
echo "${aaa}"
echo 'does not work...'
# trim "-"
echo ${aaa/+(-)}
echo ${aaa%%+(-)}
echo 'works for one symbol...'
echo ${aaa%-}
echo ${aaa/-}
The last two rows work fine but previous ones.
$ bash test.sh
---aa-aa---
does not work...
---aa-aa---
---aa-aa---
works for one symbol...
---aa-aa--
--aa-aa---
In the same time, if you would try to make this console it works:
$ aaa='---aa-aa---'
$ echo ${aaa/+(-)}
aa-aa---
$ echo ${aaa%%+(-)}
---aa-aa
So, why it doesn't work in a script?

You seem to have shopt -s extglob enabled in your interactive shell, which turns on extended globbing. This is not the default behavior, and needs to be explicitly enabled in your script. See extended pattern matching in the bash hackers wiki for details.

Related

Why doesn't echo -n work in shell on Mac?

The man page for echo says:
-n Do not print the trailing newline character. This may also be
achieved by appending `\c' to the end of the string, as is done by
iBCS2 compatible systems. Note that this option as well as the
effect of `\c' are implementation-defined in IEEE Std 1003.1-2001
(``POSIX.1'') as amended by Cor. 1-2002. Applications aiming for
maximum portability are strongly encouraged to use printf(1) to
suppress the newline character.
However this doesn't seem to work in sh on Mac:
sh-3.2$ echo $0
/bin/sh
sh-3.2$ which echo
/bin/echo
sh-3.2$ echo -n foo
-n foo
It works properly in bash:
bash-3.2$ echo $0
bash
bash-3.2$ which echo
/bin/echo
bash-3.2$ echo -n foo
foobash-3.2
FWIW this only seems to happen on Mac, on Linux it works properly:
$ echo $0
sh
$ echo -n foo
foo$
-n is a bash extension to echo. In version 3.2 (which ships with macOS), bash does not support the extension when invoked as sh. Starting with version 4.0 (some version of which is likely on your Linux box), bash does honor -n when invoked as sh.
Update: the xpg_echo option determines if bash's built-in echo is POSIX-compliant or not. In bash 3.2 (or at least the macOS build of 3.2), this option defaults to on; in bash 4.x, it defaults to off.
% sh -c 'shopt xpg_echo'
xpg_echo on
% ./sh -c 'shopt xpg_echo'
xpg_echo off
(./sh is a symlink to /usr/local/bin/bash, a local installation of bash 4.4 on my machine.)

Strange behaviour for echo with -e flag passed to bash with -c flag

I cannot understand the behaviour of this bash script (which I cut it out of a longer real use case):
# This is test.sh
cmd="echo -e \"\n\n\n\t===== Hello World =====\n\n\""
sh -c "$cmd"
What it prints is:
$ ./test.sh
-e
===== Hello World =====
$
If I remove the -e flag, everything is printed correctly, with quoted chars correctly interpreted and without the '-e' spoil: but it shouldn't be like that.
My bash is: GNU bash, version 3.2.57(1)-release (x86_64-apple-darwin17), under macOS.
In Posix mode (when run as sh), bash 3.2's echo command takes no options; -e is just another argument to write to standard output. Compare:
$ bash -c 'echo -e "a\tb"'
a b
$ sh -c 'echo -e "a\tb"'
-e a b
A literal tab is printed in both cases because Posix echo behaves the same as bash echo -e.
For this reason, printf is almost always better to use than echo to provide consistent behavior.
cmd='printf "\n\n\n\t===== Hello World =====\n\n"'
sh -c "$cmd"
sh-4.2# cat test.sh
cmd="echo -e \"\n\n\n\t===== Hello World =====\n\n\""
sh -c "$cmd"
sh-4.2# ./test.sh
===== Hello World =====
sh-4.2#
It is getting printed correctly on my machine
OK, I think I found it myself, from here:
sh, the Bourne shell, is old. Its behaviour is specified by the POSIX standard. If you want new behaviour, you use bash, the Bourne Again shell, which gets new features added to it all the time. On many systems, sh is just bash, and bash turns on a compatibility mode when run under that name.
Groan...

echo flag -n is printing out when run from script [duplicate]

How come sh UsersInput.sh gives a different output compared to bash UsersInput.sh?
My script is below:
#!/bin/bash
echo -n "Enter: ";
read usersinput;
echo "You entered, \"$usersinput\"";
bash
localhost:Bash henry$ bash UsersInput.sh
Enter: input
You entered, "input"
sh
localhost:Bash henry$ sh UsersInput.sh
-n Enter:
input
You entered, "input"
How come -n behaves properly with the first, but not with the second? What's the reason for this and is there a workaround?
From man echo:
Some shells may provide a builtin echo command which is similar or identical to this utility. Most notably, the builtin echo in sh(1) does not accept the -n option. Consult the builtin(1) manual page.
In bash, the Bourne-again shell, echo accepts the -n option whereas in sh, the Bourne shell, echo does not, so it simply echos everything you wrote, including the -n.
/bin/sh is a version of bash (not a Bourne shell) on OS X. It has POSIX mode enabled and has a few other changes as well. One of them is that the xpg_echo shell option is enabled by default so that the builtin echo conforms to POSIX.
http://pubs.opengroup.org/onlinepubs/009696799/utilities/echo.html:
Implementations shall not support any options
http://www.gnu.org/software/bash/manual/bash.html#Bash-POSIX-Mode:
44. When the xpg_echo option is enabled, Bash does not attempt to interpret any arguments to echo as options. Each argument is displayed, after escape characters are converted.
[...]
As noted above, Bash requires the xpg_echo option to be enabled for the echo builtin to be fully conformant.
You can unset xpg_echo, use /bin/echo, or preferably just use printf:
sh -c 'shopt -u xpg_echo; echo -n aa'
sh -c '/bin/echo -n aa'
sh -c 'printf %s aa'

Bash modifiers in script

If I run the following in a bash shell:
./script /path/to/file.txt
echo !$:t
it outputs file.txt and all is good.
If in my script I have:
echo $1:t
it outputs /path/to/file.txt:t
How can I get it to output file.txt as per the behaviour I see in a shell? Thanks in advance.
Use the parameter expansion syntax:
echo ${1##*/}
Modifier only work on word designators
In bash you can use the ${1##*/} expansion to get the basename of the file with all leading path components removed:
$ set -- /path/to/file
$ echo "$1"
/path/to/file
$ echo "${1##*/}"
file
You can use this in a script as well:
#!/bin/sh
echo "${1##*/}"
While ${1##*/} will work when Bash is called as /bin/sh, other Bash features require that you use #!/bin/bash at the start of your script. This notation may also not be available in other shells.
A more portable solution is this:
#!/bin/sh
echo `basename "$1"`

Cmdline Bash Variable Weirdness

I want to set an alias like so:
alias hi='TEST=ok echo $TEST'
However, it doesn't seem to work reliably.
I start with:
unalias hi
unset TEST
Then:
$ alias hi="TEST=ok echo $TEST"
$ hi
$
This is on MacOSX:
$ bash --version
GNU bash, version 3.2.17(1)-release (i386-apple-darwin9.0)
Copyright (C) 2005 Free Software Foundation, Inc.
The problem has nothing to do with aliases. Simply running
$ TEST=ok echo $TEST
$
does not echo anything (except a newline), since $TEST is expanded by the shell before the echo command is run.
Three things are happening in that statement in this order:
$TEST is expanded
TEST is assigned 'ok'
echo is executed (with TEST=ok in its environment)
Placing a semicolon between the assignment and the echo command as suggested by ghostdog74 (TEST=ok ; echo $TEST) causes the assignment to be a separate shell command executed before the echo command. The shell can then expand $TEST in the second command because it has already been set.
you forgot the semicolon
alias hi='TEST=ok ;echo $TEST'
For completeness:
$ echo 'echo $TEST'>echotest
$ unset $TEST
$ TEST=ok . ./echotest
ok
$ chmod u+x echotest
$ TEST=ok ./echotest
ok
$ echo $TEST
$
In this case, setting TEST=ok modifies the environment of the script echotest which does not expand the $TEST inside it until it's run.

Resources