I have a script which basically has a function and then I am running the an if to run the function if the kernel version is the version I need. The script in short looks like this
#!/bin/bash
postinstall() {
run some postinstall commands in here
}
UNAME=`uname -r`
if [[ $UNAME == 3.* ]]
then
postinstall
else
echo "Kernel version is not correct"
fi
When I run the commands on CLI everything it works but when I trigger the script as sh <scriptname> I get the following result:
: 313: <scriptname>.sh: [[: not found
Kernel version is not correct
Line 313 is the one with the "if".
As I noted in a comment, the shell that is installed as /bin/sh is not necessarily Bash (for example, on Ubuntu, it is often dash), and even when it is Bash, it behaves differently when run as sh instead of bash.
However, as chepner pointed out in a comment, even when run as sh (in Bash's POSIX mode), Bash recognizes the [[ command — though it doesn't recognize some other extensions to POSIX shell syntax, such as process substitution.
Given the error message you are seeing, it is clear that on your machine, sh does not recognize [[ as a valid command, which in turn means it is probably not Bash. You should therefore not run it with sh but with bash. Or run it without specifying the shell on the command line, and let the shebang (#!/bin/bash) ensure that the kernel runs the correct shell.
Related
I have an Asus router running a recent version of FreshTomato - that comes with BusyBox.
I need to run a script that was made with BASH in mind - it is an adaptation of this script - but it fails to run with this error: line 41: syntax error: bad substitution
Checking the script with shellcheck.net yields these errors:
Line 41:
for optionvarname in ${!foreign_option_*} ; do
^-- SC3053: In POSIX sh, indirect expansion is undefined.
^-- SC3056: In POSIX sh, name matching prefixes are undefined.
Line 42:
option="${!optionvarname}"
^-- SC3053: In POSIX sh, indirect expansion is undefined.
These are the lines that are causing problems:
for optionvarname in ${!foreign_option_*} ; do # line 41
option="${!optionvarname}" # line 42
# do some stuff with $option...
done
If my understanding is correct, the original script simply does something with all variables that have a name starting with foreign_option_
However, as far as I could determine, both ${!foreign_option_*} and ${!optionvarname} constructs are BASH-specific and not POSIX compliant, so there is no direct "bash to sh" code conversion possible.
I have tried to create a /bin/bash symlink that points to busybox, but I got the Read-only file system error.
So, how can I get this script to run on my router? I see only two options, but I cant figure out how to implement either:
Make BusyBox interpret the script as BASH instead of SH - can I use a specific shebang for this?
Seems like the fastest option, but only if BusyBox has a "complete" implementation of BASH
Alter the script code to not use BASH specifics.
This is safer, but since there is no "collect al variables starting with X" for SH, how can I do it?
how can I get this script to run on my router?
That easy, either:
install bash on your router or
port the script to busybox/posix compatible shell.
Make BusyBox interpret the script as BASH instead of SH - can I use a specific shebang for this?
That doesn't make sense. Busybox comes with ash shell interpreter and bash is bash. Bash can interpret bash extensions, ash can't interpret them. You can't "make busybox interpret bash" - cars don't fly, planes are for that. If you want to make a car fly, you add wings to it and make it faster. The answer to Make BusyBox interpret the script as BASH instead of SH would be: patch busybox and implement all bash extensions in it.
Shebang is used to run a file under different interpreter. Using #!/bin/bash would invoke bash, which would be unrelated to anything busybox related and busybox wouldn't be involved in it.
how can I do it?
Decide on a unrealistic maximum, iterate over variables named foreign_option_{1...some_max}, for each variable see if it is set, if it is set, cotinue the script.
for i in $(seq 100); do
optionvarname="foreign_option_${i}"
# https://stackoverflow.com/questions/3601515/how-to-check-if-a-variable-is-set-in-bash
if eval "[ -z \"\${${optionvarname}+x}\" ]"; then continue; fi;
With enough luck maybe you can use the set output. The following will fail if any variable contains a value as newline + the string that matches the regex:
for optionvarname in $(set | grep -o '^foreign_option_[0-9]\+=' | sed 's/=//'); then
Indirect expansion can be easily replaced by eval:
eval "option=\"\$${optionvarname}\""
If you really cannot install Bash on that router, here is one possible workaround, which seems to work for me in BusyBox on a Qnap NAS :
foreign_option_one=1
foreign_option_two=2
for x in one two; do
opt_var=foreign_option_${x}
eval "opt_value=\$$opt_var"
echo "$opt_var = $opt_value"
done
(But you will probably encounter more problems with moving a Bash script to busybox, so you might want to first consider alternatives like replacing the router)
MOST ANSWERS I FOUND ON HERE ONLY SEEM TO WORK FOR /bin/bash.
Tricks like $BASH_SOURCE and $SHLVL don't seem to be working with sh.
There was an answer which asked to use return, because it only works within functions and sourced scripts, and see if it generated any error but I didn't understand why on executing return on command-line I got logged out of the shell. If I "executed or sourced" a script containg return, it just exits that script. This was happening when I was on freebsd. Also I don't use any desktop environment there.
Simply typing on command line,
return
result: logged out
Executing or sourcing a script containing return:
$ cat testscript
#! /bin/sh
echo hello
return
echo hello
$ ./testscript
hello
$ . testscript
hello
$
This wasn't the case when I did the same on macOS(executed /bin/sh first). It worked perfectly fine there. There it just said
sh: return: can only `return' from a function or sourced script
just as expected.
I am looking for a solution to detect if a script is sourced in case of /bin/sh.
I am using freebsd and there I currently have default shell set to sh. I know I can install bash, but still I want to know how can I do the same for /bin/sh.
UPDATE:
I would like to mention a little more detail.
MacOS
In macOS I tried starting /bin/sh through command line, and I realised later that it is a non-login shell. So, when I types in logout there, reusult was:
sh: logout: not login shell: use `exit'
So I made /bin/sh my default shell and I am sure enough that /bin/sh was executed. When I typed in return there, the output I got is:
sh: return: can only `return' from a function or sourced script
Again just as expected. But when I typed, echo $SHELL, output was:
/bin/bash
And I checked /bin directory of of my machine and /bin/sh and /bin/bash don't seem to be linked.
FreeBSD
Now I tried executing /bin/sh there as well. The results were as follows:
$ /bin/sh
$ return
$ return
logged out on 2nd return
So in simple language it doesn't show any output if /bin/sh is a non-login shell and simply just exits that shell.
#user1934428 gave some nice amount of information in #CharlesDuffy 's answer. It's worth giving a read.
There he mentions that FreeBSD manual has no documentation for return statement.
sh man page, FreeBSD
I checked if OpenBSD has the same case for man page, but it did define return as:
return [n] Exit the current function or . script with exit status n, or that of the last command executed.
sh man page, OpenBSD
One other issue is most man pages show bash manual on asking for man sh. Idk if its supposed to be like that or no.
Also, can someone suggest if I should start a new question for undefined behaviour of return? Because I think this question has went really off-topic. Not sure if it would be a good idea to do so.
$ sh ./detect-sourcing.sh
We were executed
$ sh -c '. ./detect-sourcing.sh'
We were sourced
#!/bin/sh
if (return 2>/dev/null); then
echo "We were sourced"
else
echo "We were executed"
fi
I haven't analyzed whether this is strictly required by the POSIX sh standard, but it works with /bin/sh on MacOS, the OP's stated platform.
I found the answer to this through FreeBSD mailing lists.
The man page where the entry for return was missing was the wrong man page.
Looking at the correct man page, the complete behaviour of return statement has been stated.
The syntax of the return command is
return [exitstatus]
It terminates the current executional scope, returning from the closest
nested function or sourced script; if no function or sourced script is
being executed, it exits the shell instance. The return command is im-
plemented as a special built-in command.
As suggested by Ian in mailing lists, in case of /bin/sh a good possible way seems to keep a fixed name for your script and expand $0:
${0##*/}
and match it with the name. If the expansion produces anything else, it means script has been sourced. Another case could be that the user renamed it. So it's not completely error-prone but still should get my job done.
If you plan to use Bourne shell (/bin/sh) only testing $0 works nice.
$ cat t
#!/bin/sh
if [ $0 == "sh" ]; then
echo "sourced"
else
echo executed
fi
$ . t
sourced
$ . ./t
sourced
$ ./t
executed
$ sh t
executed
$ sh ./t
executed
If you want to call or source the script from other shells test $0 against list of shell names.
As #Mihir pointed FreeBSD shell works as described in manual page sh(1).
In MacOS /bin/sh is basically bash albeit files /bin/sh and /bin/bash slightly differ.
Note that comand man sh on Mac brings manual page for bash
I have a bash script in which I define the below function,
function start-if-exists()
{
if [ "`docker container ls -a|grep $1`" ]; then
echo "Container $1 exists. Starting $1..."
return `docker start $1`
else
echo "Container $1 doesn't exists."
return ""
fi
}
While executing the above function in terminal(zsh) directly I am not getting any error. But when I execute it using sh command(sh my_script.sh), I am getting the below error.
my_script.sh: line 10: `start-if-exists': not a valid identifier
where my_script.sh is the name of file.
What am I missing that my script works with zsh but fails in sh?
/bin/sh is bash, but, when started as /bin/sh, it starts in POSIX mode. According to the bash man page, in POSIX mode:
Function names must be valid shell `name's. That is, they may not
contain characters other than letters, digits, and underscores, and
may not start with a digit. Declaring a function with an invalid
name causes a fatal syntax error in non-interactive shells.
A note about how to figure things like this out:
At the Terminal command line, I executed /bin/sh --version to see information about it. It printed “GNU bash, version 3.2.57(1)-release (x86_64-apple-darwin17)”.
Then I referred to the bash man page, using the command man bash. Since that is long, you might prefer to save a copy to a file and view it in your preferred text editor. The raw man output includes archaic underscores and backspaces. You can get a copy without these by executing man bash | col -b > file.txt.
In the man page, I searched for ”sh” (as a complete word, not a raw search for those letters, since they appear as parts of many unrelated words). This quickly revealed discussion that bash behaves differently when started as “sh”; it starts in POSIX mode.
Searching further for “POSIX” revealed a list of things that are different in POSIX mode.
I am trying to create an AppleScript with commands below. An issue I am having is there is an error at the third line. I have no problem using the lame command in the terminal directly. In addition, lame is not a native Mac utility; I installed it on my own. Does anybody have a solution?
do shell script "cd ~/Downloads"
do shell script "say -f ~/Downloads/RE.txt -o ~/Downloads/recording.aiff"
do shell script "lame -m m ~/Downloads/recording.aiff ~/Downloads/recording.mp3"
-- error "sh: lame: command not found" number 127
do shell script "rm recording.aiff RE.txt"
To complement Paul R's helpful answer:
The thing to note is that do shell script - regrettably - does NOT see the same $PATH as shells created by Terminal.app - a notable absence is /usr/local/bin.
On my OS X 10.9.3 system, running do shell script "echo $PATH" yields merely:
/usr/bin:/bin:/usr/sbin:/sbin
There are various ways around this:
Use the full path to executables, as in Paul's solution.
Manually prepend/append /usr/local/bin, where many non-system executables live, to the $PATH - worth considering if you invoke multiple executables in a single do shell script command; e.g.:
do shell script "export PATH=\"/usr/local/bin:$PATH\"
cd ~/Downloads
say -f ~/Downloads/RE.txt -o ~/Downloads/recording.aiff
lame -m m ~/Downloads/recording.aiff ~/Downloads/recording.mp3
rm recording.aiff RE.txt"
Note how the above use a single do shell script command with multiple commands in a single string - commands can be separated by newlines or, if on the same line, with ;.
This is more efficient than multiple invocations, though adding error handling both inside the script code and around the do shell script command is advisable.
To get the same $PATH that interactive shells see (except additions made in your bash profile), you can invoke eval $(/usr/libexec/path_helper -s); as the first statement in your command string.
Other important considerations with do shell script:
bash is invoked as sh, which results in changes in behavior, most notably:
process substitution (<(...)) is not available
echo by default accepts no options and interprets escape sequences such as \n.
other, subtle changes in behavior; see http://www.gnu.org/software/bash/manual/html_node/Bash-POSIX-Mode.html
You could address these issues manually by prepending shopt -uo posix; shopt -u xpg_echo; to your command string.
The locale is set to the generic "C" locale instead of to your system's; to fix that, manually prepend export LANG='" & user locale of (system info) & ".UTF-8' to your command string.
No startup files (profiles) are read; this is not surprising, because the shell created is a noninteractive (non-login) shell, but sometimes it's handy to load one's profile by manually by prepending . ~/.bash_profile to the command string; note, however, that this makes your AppleScript less portable.
do shell script command reference: http://developer.apple.com/library/mac/#technotes/tn2065/_index.html
Probably a PATH problem - use the full path for lame, e.g.
do shell script "/usr/local/bin/lame -m m ~/Downloads/recording.aiff ~/Downloads/recording.mp3"
I have been struggling to get the path of an installed BASH command via Applescript for a long time. Using the information here, I finally succeeded.
tell me to set sox_path to (do shell script "eval $(/usr/libexec/path_helper -s); which sox")
Thanks.
Url:http://sourceforge.net/project/showfiles.php?group_id=290&package_id=309
./configure
make install
I've a file new.sh
i=5
i=$[i+1]
echo $i
when I execute ./new.sh, it shows 6. But when I execute "sh new.sh", it shows
$[i+1]
as output. I just want to know why, and I need a code which will work on both.
Many Linux distributions use dash as their standard shell for scripts and therefore /bin/sh is only a symlink to /bin/dash, which is more lightweight, but lags some functionality compared with bash. Check that with:
ls -l /bin/sh
If you want to write POSIX compatible scripts, you should use foo=$n; $((n=n+1)) instead of foo=$((n++)) and foo=$((n=n+1)) instead of foo=$((++n)). The form $[] is deprecated and should be avoided.
The OS decides what type of file it is based on the first line. Make it:
#!/bin/bash
The behaviour is different because when bash is run as sh, it turns off bash-specific features and hews more closely to the POSIX shell standard.
If you want your code to work the same under both bash and sh, you will need to write your code in the sh subset of the bash syntax.