I would like to keep my remotelogin to always be "On". In this case, a .plist is called and runs a shell script (the one above) to see if RemoteLogin is Off, if so then run the following two commands; else, exit 0.
Is there something I'm missing here? Maybe "Off" needs to be replace with 0?
if [ `sudo systemsetup -getremotelogin` = "Off" ]; then
sudo systemsetup -setremotelogin on
sudo launchctl load -w /System/Library/LaunchDaemons/ssh.plist
else
exit 0
fi
You should test for "Remote Login: Off" instead of just "Off".
When I execute that command I get the following:
Blackintosh:~ jhr$ sudo systemsetup -getremotelogin
Remote Login: Off
Using:
Blackintosh:~ jhr$ sw_vers
ProductName: Mac OS X
ProductVersion: 10.10.5
BuildVersion: 14F27
This is more what you want it to look like:
#!/usr/bin/env bash
if [ "`sudo systemsetup -getremotelogin`" = "Remote Login: Off" ]; then
sudo systemsetup -setremotelogin on
sudo launchctl load -w /System/Library/LaunchDaemons/ssh.plist > /dev/null 2>&1
sudo systemsetup -getremotelogin
fi
I usually assign command executions to a variable like you would do for a function call and for maintainability. You usually end up needing the output multiple places in non-trivial scripts.
remoteLoginState=`sudo systemsetup -getremotelogin`
if [ "$remoteLoginState" = "Remote Login: Off" ]; then
...
fi
And make sure you set the executable flag on the script.
To complement Jarrod Roberson's helpful answer:
The problem was your use of a POSIX-style conditional, [ ... ], without quoting the LHS:
Such conditionals are parsed with command syntax meaning that the result of `sudo systemsetup -getremotelogin` - due to being unquoted - is subject to word-splitting and pathname expansion:
sudo systemsetup -getremotelogin yields literal string Remote Login: Off, to which word-splitting (and pathname expansion, which happens to be a no-op here) is applied, and the shell then executes [ as if the following had been specified:
[ 'Remote' 'Login:' 'Off' = 'Off' ]
As you can see, there are now 3 LHS arguments instead of 1, which violates the syntax of the [ shell builtin and results in the [: too many arguments error you saw.
One possible fix is to double-quote the command substitution AND to fix the RHS to match the entire string output by the command substitution, as in Jarrod's answer.
If you're trying to write a portable, POSIX-compliant script, that is the way to go.
If, by contrast, you're specifically targeting bash, there are better options with more flexibility:
if [[ $(sudo systemsetup -getremotelogin) =~ 'Off' ]]; then
OR:
if [[ $(sudo systemsetup -getremotelogin) == *'Off'* ]]; then
Note how the command substitution $(...) (which is the superior alternative to `...`) now does not need to be quoted, because bash's [[ ... ]] construct is parsed differently from [ ... ]; however, you may still double-quote for the sake of consistency.
=~ applies extended regular-expression matching to the unquoted parts of the RHS; with 'Off' as the RHS - a quoted string - this effectively provides substring matching.
== applies glob-style pattern matching to the unquoted parts of the RHS and matches the entire LHS; *'Off'* therefore effectively provides substring matching too.
Related
The zsh_test.sh is simple, as following:
#!/usr/bin/env zsh
if [ $USER == 'root' ]; then
echo "root"
else
echo "not root"
fi
Copy and paste the above codes into a zsh shell, it executed well.
$ #!/usr/bin/env zsh
$ if [ $USER == 'root' ]; then
then> echo "root"
then> else
else> echo "not root"
else> fi
not root
But directly execute script file zsh_test.sh, got an error.
$ ./zsh_test.sh
./zsh_test.sh:3: = not found
I now see what's wrong: You are the victim of a fairly obscure zsh mechanism, which is described in the zshexpn man page and is called '=' expansion. From the man-page:
If a word begins with an unquoted `=' and the EQUALS option is set, the remainder of the word is taken as the name of a command. If a command exists by that name, the word is replaced by the full pathname of the command.
You can try it with the command
echo ==
which also outputs this error message. For instance, on my platofm
echo =ruby
outputs /usr/bin/ruby, because this is where I have ruby installed. If you would have in your PATH a program named =, the == would resolve to this path.
While it is unusual to use a double == sign inside [ ... ], the zsh implementation of this command allows it, but you would have to quote the operator, to avoid =-expansion:
if [ $USER '==' root ]; then
An alternative would be to use [[ ... ]] instead. This is not a command, but a syntactic construct, and expansion rules are different inside it. Therefore
if [[ $USER == root ]]; then
would work as well.
I'm afraid that you are using test command wrong. Let's see why.
test command is defined since Unix version III. You can often find this command also as [ binary in your PATH. In most modern shells (let's pretend that bash is modern shell as well), there is also implementation of test or [ as builtin command. From the specification, the only valid way to compare two strings is this:
STRING1 = STRING2
the strings are equal
STRING1 != STRING2
the strings are not equal
Original strict POSIX implementation of test command is somehow limited and can be difficult to use. But it is portable, and that it's main strength. But what if you don't care about portability at all? Then there are Conditional Expressions.
Conditional Expressions, available as [[ builtin command, are improved, not POSIX compatible replacement for original test command. Look in the manual for the things you can compare with them to get the idea. Double equality sign (==) is also supported (and the documentation explicitly says it's for compatibility with other sorts of computer language.)
Conclusion?
When you are writing scripts for particular shell, like zsh, and you are absolutely sure that portability is not important for you, always use [[ instead of [. Your life will be easier and change to your script is minimal:
#!/usr/bin/env zsh
if [[ $USER == 'root' ]]; then
echo "root"
else
echo "not root"
fi
If portability between different shells and environments is necessary, you will have to use original test or [ command, and forget about zsh, == and many other things at all.
#!/bin/sh
if [ "$USER" = 'root' ]; then
printf '%s\n' "root"
else
printf '%s\n' "not root"
fi
The zsh_test.sh is simple, as following:
#!/usr/bin/env zsh
if [ $USER == 'root' ]; then
echo "root"
else
echo "not root"
fi
Copy and paste the above codes into a zsh shell, it executed well.
$ #!/usr/bin/env zsh
$ if [ $USER == 'root' ]; then
then> echo "root"
then> else
else> echo "not root"
else> fi
not root
But directly execute script file zsh_test.sh, got an error.
$ ./zsh_test.sh
./zsh_test.sh:3: = not found
I now see what's wrong: You are the victim of a fairly obscure zsh mechanism, which is described in the zshexpn man page and is called '=' expansion. From the man-page:
If a word begins with an unquoted `=' and the EQUALS option is set, the remainder of the word is taken as the name of a command. If a command exists by that name, the word is replaced by the full pathname of the command.
You can try it with the command
echo ==
which also outputs this error message. For instance, on my platofm
echo =ruby
outputs /usr/bin/ruby, because this is where I have ruby installed. If you would have in your PATH a program named =, the == would resolve to this path.
While it is unusual to use a double == sign inside [ ... ], the zsh implementation of this command allows it, but you would have to quote the operator, to avoid =-expansion:
if [ $USER '==' root ]; then
An alternative would be to use [[ ... ]] instead. This is not a command, but a syntactic construct, and expansion rules are different inside it. Therefore
if [[ $USER == root ]]; then
would work as well.
I'm afraid that you are using test command wrong. Let's see why.
test command is defined since Unix version III. You can often find this command also as [ binary in your PATH. In most modern shells (let's pretend that bash is modern shell as well), there is also implementation of test or [ as builtin command. From the specification, the only valid way to compare two strings is this:
STRING1 = STRING2
the strings are equal
STRING1 != STRING2
the strings are not equal
Original strict POSIX implementation of test command is somehow limited and can be difficult to use. But it is portable, and that it's main strength. But what if you don't care about portability at all? Then there are Conditional Expressions.
Conditional Expressions, available as [[ builtin command, are improved, not POSIX compatible replacement for original test command. Look in the manual for the things you can compare with them to get the idea. Double equality sign (==) is also supported (and the documentation explicitly says it's for compatibility with other sorts of computer language.)
Conclusion?
When you are writing scripts for particular shell, like zsh, and you are absolutely sure that portability is not important for you, always use [[ instead of [. Your life will be easier and change to your script is minimal:
#!/usr/bin/env zsh
if [[ $USER == 'root' ]]; then
echo "root"
else
echo "not root"
fi
If portability between different shells and environments is necessary, you will have to use original test or [ command, and forget about zsh, == and many other things at all.
#!/bin/sh
if [ "$USER" = 'root' ]; then
printf '%s\n' "root"
else
printf '%s\n' "not root"
fi
Inside my project folder if I use Bash and run . ./setantenv.sh, it works.
But in the same folder if I use ZSH and try to run . ./setantenv.sh, it results in ./setantenv.sh:4: = not found
ZSH doesn't have any issues to run all the other commands. But it seems that it doesn't recognise the first . before the ./AnyCommand.sh
#!/bin/bash
OWN_NAME=setantenv.sh
if [ "$0" == "./$OWN_NAME" ]; then
echo * Please call as ". ./$OWN_NAME", not ./$OWN_NAME !!!---
echo * Also please DO NOT set back the executable attribute
echo * On this file. It was cleared on purpose.
chmod -x ./$OWN_NAME
exit
fi
PLATFORM_HOME=`pwd`
export -p PLATFORM_HOME
export -p ANT_OPTS="-Xmx400m -XX:MaxPermSize=128M"
export -p ANT_HOME=$PLATFORM_HOME/apache-ant-1.9.1
chmod +x "$ANT_HOME/bin/ant"
export -p PATH=$ANT_HOME/bin:$PATH
echo Setting ant home to: $ANT_HOME
ant -version
Any help?
if [ "$0" "==" "./$OWN_NAME" ]; then
or
if [ "$0" = "./$OWN_NAME" ]; then
rewrite like this when runs zsh.
this is the latest POSIX specification for test and its alias, [. as you can see, there's no == in the whole page.
freebsd test(1) man page says:
COMPATIBILITY
For compatibility with some other implementations, the = primary
can be substituted with == with the same meaning.
but zsh has [ builtin:
% for sh in sh bash mksh zsh; do $sh -c "printf '%-5s- ' $sh; which '['"; done
sh - /bin/[
bash - /bin/[
mksh - /bin/[
zsh - [: shell built-in command
... and zsh's version does not do ==. from zshbuiltins(1) (only the outer brackets on the second synopsis line are actual syntax, the rest is *BNF):
test [ arg ... ]
[ [ arg ... ] ]
Like the system version of test. Added for compatibility; use
conditional expressions instead (see the section `Conditional
Expressions'). The main differences between the conditional
expression syntax and the test and [ builtins are: these
commands are not handled syntactically, so for example an empty
variable expansion may cause an argument to be omitted; syntax
errors cause status 2 to be returned instead of a shell error;
and arithmetic operators expect integer arguments rather than
arithmetic expressions.
The command attempts to implement POSIX and its extensions where
these are specified. Unfortunately there are intrinsic
ambiguities in the syntax; in particular there is no distinction
between test operators and strings that resemble them. The
standard attempts to resolve these for small numbers of
arguments (up to four); for five or more arguments compatibility
cannot be relied on. Users are urged wherever possible to use
the `[[' test syntax which does not have these ambiguities.
btw, the error message you got says = not found because zsh uses leading = for filename expansion. from zshexpn(1):
'=' expansion
If a word begins with an unquoted '=' and the EQUALS option is set, the
remainder of the word is taken as the name of a command. If a command
exists by that name, the word is replaced by the full pathname of the
command.
apparently, there's no command named = anywhere in your $PATH. :)
% echo =ls
/bin/ls
% echo =fubar
zsh: fubar not found
% echo ==
zsh: = not found
I'm confused about this conditional:
if [[ ! -z "$1" ]]
What is this language?
Here is what I'm familiar with for my terminal and bash_profile:
Bash is the shell, or command language interpreter, for the GNU
operating system.
and
Simply put, the shell is a program that takes your commands from the
keyboard and gives them to the operating system to perform. In the old
days, it was the only user interface available on a Unix computer.
Nowadays, we have graphical user interfaces (GUIs) in addition to
command line interfaces (CLIs) such as the shell.
On most Linux systems a program called bash (which stands for Bourne
Again SHell, an enhanced version of the original Bourne shell program,
sh, written by Steve Bourne) acts as the shell program.
function parse_git_branch {
branch=`git rev-parse --abbrev-ref HEAD 2>/dev/null`
if [ "HEAD" = "$branch" ]; then
echo "(no branch)"
else
echo "$branch"
fi
}
function prompt_segment {
# for colours: http://en.wikipedia.org/wiki/ANSI_escape_code#Colors
# change the 37 to change the foreground
# change the 45 to change the background
if [[ ! -z "$1" ]]; then
echo "\[\033[${2:-37};45m\]${1}\[\033[0m\]"
fi
}
function build_mah_prompt {
# time
ps1="$(prompt_segment " \# ")"
# cwd
ps1="${ps1} $(prompt_segment " \w ")"
# git branch
git_branch=`parse_git_branch`
if [[ ! -z "$git_branch" ]]
then
ps1="${ps1} $(prompt_segment " $git_branch " 32)"
fi
# next line
ps1="${ps1}\n\$ "
# set prompt output
PS1="$ps1"
}
PROMPT_COMMAND='build_mah_prompt'
The language is Bash, a modern shell based on the old Bourne shell and
(mostly) compatible with POSIX standards.
test aka [
[[ is a Bash extension of the test command also known as [. The test command is a separate executable but since it’s so useful for shell programming, most (if not all) modern shells implement it as a shell builtin. The following commands show that both versions are available on many systems:
$ type -a test
test is a shell builtin
test is /usr/bin/test
$ type -a [
[ is a shell builtin
[ is /usr/bin/[
For more info, see man test or help test (with Bash).
[[
[[ is implemented as a Bash keyword (not an external command). It originally came from the Korn shell and works similarly but has many improvements over the original [ command. See the following for more info:
What is the difference between test, [ and [[ ?
What's the difference between [ and [[ in bash?
Is [[ ]] preferable over [ ] in bash scripts?
Specific example
According to man test (POSIX specification)
−z string True if the length of string string is zero; otherwise, false.
Thus, the [[ -z "$1" ]] construct returns 0 (value for True in Unix shells) if $1, the first positional parameter to a script or function is an empty string. Introducing the negation ! operator converts the expression to its Boolean opposite, i.e, False if the following expression evaluates to True and vice versa.
To sum up, the whole expression evaluates to True if the first argument to the function is a non-empty string and False if it’s empty (or possibly not set at all).
If you read the above links, you’ll notice that [[ ! -z "$1" ]] is actually equivalent to [[ -n "$1" ]] which return True if $1contains anything, i.e., is not empty. This can be further shortened to [[ $1 ]] as quotes aren’t required for variables within [[.
Note: the portable version (for POSIX shells) is [ -n "$1" ] or [ "$1" ] (where the variables have to be quoted to protect from pathname expansion, word splitting and other potential side effects). See http://mywiki.wooledge.org/Quotes for more info.
Functions
The remaining code are shell functions which look like they’re used to build up a colourful prompt which provides details of the status of a git repository if the current working directory is under version control.
This question already has answers here:
Is there a list of 'if' switches anywhere?
(5 answers)
Closed 5 years ago.
What function do the -a and -n options perform in the following bash if statement?
if [ -n "$1" -a -n "$2" -a -n "$3" ]; then
REFNAME=$(basename $3)
else
Are -a and -n so called primaries?
Does -a file mean "True if file exists."?
-a Links two expressions together in an "and" or "&&" expression. This option is deprecated.
-n Checks if the length of a string is nonzero.
You could translate the test expression into the following pseudocode:
if ( ($1 has nonzero length) and
($2 has nonzero length) and
($3 has nonzero length) )
There are no checks in that expression for whether the file exists or doesn't exist, only whether the arguments have been supplied to the script.
The arguments -a and -n can be found in the manpage for test
man test
The operator [ ... ] is often used as shorthand for test ... and likely has identical functionality on your system.
Nitpicking
The switches -a and -n are not strictly part of a bash if statement in that the if command does not process these switches.
What are primaries?
I call them "switches", but the bash documentation that you linked to refers to the same thing as "primaries" (probably because this is a common term used when discussing parts of a boolean expression).
Background and docs
In sh scripts if is a command that takes a command as its argument, executes it and tests its return code. If the return code is 0 the block of code following then is executed up until the closing fi or (if supplied) the following else. If the return code was not 0 and an else statement was supplied then the block of code following else is executed up until the closing fi.
You can see this effect by passing if the command true or the command false, which are simple commands that do nothing and return 0 and non-0 respectively.
if true ; then echo true was true ; else echo true was false ; fi
if false ; then echo false was true ; else echo false was false ; fi
In the sample code you provided the command that you're passing to if is [, which is also sometimes known as test. It is this command which takes the switches you're asking about. In bash the test command will be a built-in command; try type [ to learn its type. For built-in commands help will show usage, so also run help [ to see documentation. Your system probably also has a /bin/[ and a /bin/test and if you man test you can see the manuals for those. Although the behavior of the built-in test may not be identical to the behavior documented in the man pages, which is likely more verbose than the simple description you'll get from help [, it will probably describe the behavior of the built-in [ command fairly accurately.
The behavior of -a and -n
Knowing that the command you're running is test we can consult help test or man test and read its usage. This will show that-n tests the following argument and evaluates to true if it is not an empty string.
In the documentation of test you will also see a the switch -e. This switch tests the following argument and evaluates to true if that argument is a file or directory that exists. More useful still is the -f switch which evaluates to true if the following argument exists and is a regular file (as opposed to a directory or a block device, or whatever).
The source of your confusion is probably that there can be two forms of -a: Unary and binary. When -a is used in a unary context, that is with one following argument but no preceding arguments, it treats its argument as a file and tests for its existence, just like the -e switch. However, when -a is used in a binary context, that is with one argument before it and one argument after it, it treats its arguments as other conditions and acts as a boolean AND operator.
In the interests of portability it is important to note that unary -a is a non-standard extension which won't be found in POSIX. It is available in bash and ksh, however, so usage is probably widespread.
Example
cd /tmp
if [ -a test-file ] ; then
echo 1: test-file exists
else
echo 1: test-file missing
fi
touch test-file
if [ -a test-file ] ; then
echo 2: test-file exists
else
echo 2: test-file missing
fi
var=somerthing
if [ -n "$var" -a -a test-file ] ; then
echo variable var is not empty and test-file exists
fi
rm -f test-file