Mac OSx terminal : "not a valid identifier" on function definition - shell

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.

Related

Converting a BASH script to run on SH (via BusyBox)

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)

Detect if a script has been sourced in "/bin/sh"

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

if not working with wildcard value

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.

getting last executed command from script

I'm trying to get last executed command from command line from a script to be saved for a later reference:
Example:
# echo "Hello World!!!"
> Hello World!!!
# my_script.sh
> echo "Hello World!!!"
and the content of the script would be :
#!/usr/bin/ksh
fc -nl -1 | sed -n 1p
Now as you notices using here ksh and fc is a built in command which if understood correctly should be implemented by any POSIX compatible shells. [I understand that this feature is interactive and that calling same fc again will give different result but this is not the concern do discuss about]
Above works so far so good only if my_script.sh is being called from the shell which is as well ksh, or if calling from bash and changing 1st line of script as #!/bin/bash then it works too and it doesn't if shells are different.
I would like to know if there is any good way to achieve above without being constrained by the shell your script is called from. I understand that fc is a built in command and it works per shell thus most probably my approach is not good at all from what I want to achieve. Any better suggestions?
I actually attempted this, but it cannot be done between different shells consistently.
While
fc -l`,
is the POSIX standard command for showing $SHELL history, implementation details may be different.
At least bash and ksh93 both will report the last command with
fc -n -l -1 -1
However, POSIX does not guarantee that shell history will be carried over to a new instance of the shell, as this requires the presence of a $HISTFILE. If none is
present, the shell may default to $HOME/.sh_history.
However, this history file or Command History List is not portable between different shells.
The POSIX Shell description of the
Command History List says:
When the sh utility is being used interactively, it shall maintain a list of commands
previously entered from the terminal in the file named by the HISTFILE environment
variable. The type, size, and internal format of this file are unspecified.
Emphasis mine
What this means is that for your script needs to know which shell wrote that history.
I tried to use $SHELL -c 'fc -nl -1 -1', but this did not appear to work when $SHELL refers to bash. Calling ksh -c ... from bash actually worked.
The only way I could get this to work is by creating a function.
last_command() { (fc -n -l -1 -1); }
In both ksh and bash, this will give the expected result. Variations of this function can be used to write the result elsewhere. However, it will break whenever it's called
from a different process than the current.
The best you can do is to create these functions and source them into your
interactive shell.
fc is designed to be used interactively. I tested your example on cygwin/bash and the result was different. Even with bash everywhere the fc command didn't work in my case.
I think fc displays the last command of the current shell (here I don't speak about the shell interpretor, but shell as the "process box". So the question is more why it works for you.
I don't think there is a clean way to achieve what you want because (maybe I miss something) you want two different process (bash and your magic command [my_script.sh]) and by default OS ensure isolation between them.
You can rely on what you observe (not portable, depends on the shell interpretor etc.)
You cannot rely on BASH historic because it's in-memory (the file is updated only on exit).
You can use an alias or a function (edited: #Charles Duffy is right). In this case you won't be able to use your "magic command" from another terminal, but for an interactive use it does the job.
Edited:
Or you can provide two commands: one to save and another to look for. In this case you manage your own historic but you have to save explicitly each command that is painful...
So I look for a hook. And I found this other thread : https://superuser.com/questions/175799/does-bash-have-a-hook-that-is-run-before-executing-a-command
# At the beginning of the Shell (.bashrc for example)
save(){ history 1 >>"$HOME"/myHistory ; }
trap 'save' DEBUG
# An example of use
rm -f "$HOME"/myHistory
echo "1 2 3"
cat "$HOME"/myHistory
14 echo "1 2 3"
15 cat "$HOME"/myHistory
But I observe it slows down the interpretor...
Little convoluted, but I was able to use this command to get the most recent command in zsh, bash, ksh, and tcsh on Linux:
history | tail -2 | head -1 | sed -r 's/^[ \t]*[0-9]*[ \t]+([^ \t].*$)/\1/'
Caveats: this uses GNU sed, so you'll need to install that if you're using BSD, OS X, etc; and also, tcsh will display the time of the command before the command itself. Regular csh doesn't seem to having a functioning history command when I tried it.

AppleScript : error "sh: lame: command not found" number 127

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

Resources