shell scripting, if is executed no matter what - sh

why in this script
#!/bin/sh
if [ $1='all' ];
then
echo $1
echo "all"
fi
all is printed no matter what???
For example I do: ./thescript.sh and I got clearly that $1 is empty. But still all is printed. Why? And how can I print "all" when all is passed

shellcheck has the solution:
SC2077: You need spaces around the comparison operator.
Change to
if [ $1 = 'all' ];
Without the spaces, the thing between the [ and ]is treated as one expression, and man [ says
"STRING equivalent to -n STRING
so any non-empty string is considered true. (i.e., if [ "" ]; would be false.)
That said, you better specify which shell you want in the shebang. On some systems /bin/sh may be bash, on others /bin/dash, or something else. Pick your shell and avoid any problems from bad assumptions on which shell you actually get.

Put double-quotes around the variable:
#!/bin/sh
if [ "$1" == "all" ];
then
echo $1
echo "all"
fi

Related

Can fish shell do wildcard globbing in an if statement?

I'm having trouble understanding whether I can use wildcard globbing in an if statement in fish. This switch/case works as expected:
# correctly echos macOS on macOS
switch "$OSTYPE"
case 'darwin*'
echo 'macOS'
case '*'
echo 'not macOS'
end
However, I cannot get an if statement version of the same thing to work.
# doesn't work - prints 'not macOS' on macOS
if [ "$OSTYPE" = 'darwin*' ]
echo 'macOS'
else
echo 'not macOS'
end
In zsh/bash you can do something like this:
[[ $OSTYPE == darwin* ]] && echo 'macOS' || echo 'not macOS'
Or, more verbosely,
if [[ $OSTYPE == darwin* ]]
then echo 'macOS'
else echo 'not macOS'
fi
My question is, does fish support wildcard globbing against a variable in if statements? Am I doing this wrong? I cannot find an example in the fish docs that tells me either way.
NOTE: I'm not asking about checking $OSTYPE in fish. I know there are better ways to do that. My question is limited strictly to whether it is possible to do wildcard globbing in an if statement in fish.
No.
Use switch like you said, or the string builtin like
if string match -q 'darwin*' -- "$OSTYPE"
The if isn't important - the command you are running in your example is [, which is an alternative name for test, which is a builtin with documentation at http://fishshell.com/docs/current/commands.html#test (or man test or help test).

Conditional expression with sh: errors from both [[ $COUNT == 0 ]] and [ $COUNT == 0 ]

Why is it that the following snipped of code return the following message: syntax error in conditional expression and fails.
#!/bin/sh
COUNT=`cat annemarie/new_files.txt | wc -l`
if [[ $COUNT -ge 1 ]]; then
echo "New files found. Stopping deployment"
exit 0
fi
if [[ $COUNT == 0 ]]; then
echo "File not found. Continuing deployment"
fi
but this passes, but gives me the warning unary operator expected, however it doesnt seem as if the code is processed:
#!/bin/sh
COUNT=`cat annemarie/new_files.txt | wc -l`
if [ $COUNT -ge 1 ]; then
echo "New files found. Stopping deployment"
exit 0
fi
if [ $COUNT == 0 ]; then
echo "File not found. Continuing deployment"
fi
Which is the correct format?
With /bin/sh, [[ is not guaranteed to be supported whatsoever -- but == inside of [ ] is also not guaranteed to work. Thus, your code must be:
#!/bin/sh
count=$(wc -l <annemarie/new_files.txt)
if [ "$count" -ge 1 ]; then
echo "New files found. Stopping deployment"
exit 0
fi
if [ "$count" -eq 0 ]; then
echo "File not found. Continuing deployment"
fi
Note:
[[ ... ]] is a ksh extension also picked up by bash and zsh. While this has several benefits (among them, preventing string-splitting and glob expansion, and thus making quotes less necessary), it is not specified as part of the POSIX sh standard, and so is not guaranteed to be available at all when a script is run with /bin/sh.
$(...) is modern command substitution syntax; it has more predictable behavior when nested or when running commands that contain literal backslashes than does the old, pre-POSIX backtick-based syntax.
For string comparisons, == should be replaced with = for portability purposes; see the POSIX test specification, in which = is the only string comparison operator. That said, inasmuch as what we want is a numeric comparison, -eq is arguably the correct operator for the job.
Expansions must be quoted to prevent string-splitting and globbing.
cat foo | bar is potentially much less efficient than bar <foo. (With some commands this is only a minor difference; for commands that can parallelize or seek when given a direct file handle -- such as sort or tail -- it can be much larger)
The POSIX standard reserves all-caps names for environment variables and shell builtin names with meaning to the OS and shell themselves, and guarantees that lower-case names will not unexpectedly modify the behavior of POSIX-defined tools. See http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap08.html, fourth paragraph.

How to check substring in Bourne Shell?

I wanna test whether a string has "substring". Most answers online is based on Bash. I tried
if [ $string == "*substring*" ]
which was not working. Currently
if echo ${string} | grep -q "substring"
worked. Is there any other better way.
Using POSIX compliant parameter-expansion and with the classic test-command.
#!/bin/sh
substring=ab
string=abc
if [ "$string" != "${string%"$substring"*}" ]; then
echo "$substring present in $string"
fi
(or) explicitly using the test operator as
if test "$string" != "${string%$substring*}" ; then
In a POSIX-features only shell you won't be able to do general pattern or regex matching inside a conditional without the help of an external utility.
That said:
Kenster's helpful answer shows how to use the branches of a case ... esac statement for pattern matching.
Inian's helpful answer shows how to match indirectly inside a conditional, using patterns as part of parameter expansions.
Your own grep approach is certainly an option, though you should double-quote ${string}:
if echo "${string}" | grep -q "substring"; ...
A slightly more efficient way is to use the expr utility, but note that per POSIX it is limited to BREs (basic regular expressions), which are limited:
string='This text contains the word "substring".'
if expr "$string" : ".*substring" >/dev/null; then echo "matched"; fi
Note that the regex - the 3rd operand - is implicitly anchored at the start of the input, hence the need for .*.
>/dev/null suppresses expr's default output, which is the length of the matched string in this case. (If nothing matches, the output is 0, and the exit code is set to 1).
If you're just testing for substrings (or anything that can be matched using filename wildcards) you can use case:
#!/bin/sh
while read line; do
case "$line" in
*foo*) echo "$line" contains foo ;;
*bar*) echo "$line" contains bar ;;
*) echo "$line" isnt special ;;
esac
done
$ ./testit.sh
food
food contains foo
ironbar
ironbar contains bar
bazic
bazic isnt special
foobar
foobar contains foo
This is basic Bourne shell functionality. It doesn't require any external programs, it's not bash-specific, and it predates POSIX. So it should be pretty portable.
Short answer is no, not if you are trying to use vanilla sh, without Bash extensions. On many modern systems, /bin/sh is actually a link to /bin/bash, which provides a superset of sh's functionality (for the most part). Your original attempt would have worked with Bash's builtin [[ extended test command: http://mywiki.wooledge.org/BashFAQ/031

Shell script to build CLI args for a PERL script

I have a Jenkins job, triggered as a parameterized build. It accepts an optional String parameter (HOSTNAMES) that can be a comma separated list of hostnames.
I need to pass this comma separated list of hostnames as a command line argument to a PERL script (within Execute shell build step).
Here is how I process the input parameter and construct the command line argument within the execute shell build step:
cmd_options=''
echo hostnames is $HOSTNAMES
if [ "$HOSTNAMES" != "" ]
then
cmd_options+=" --hostnames \"$HOSTNAMES\""
fi
perl myscript.pl $cmd_options
In the console output of the build though, I see the argument being passed incorrectly. Here is the console output:
+ cmd_options=
+ echo hostnames is host1, host2
hostnames is host1, host2
+ '[' 'host1, host2' '!=' '' ']'
+ cmd_options+=' --hostnames "host1, host2"'
+ perl myscript.pl --hostnames '"host1,' 'host2"'
I want myscript.pl to be called this way:
perl myscript.pl --hostnames "host1, host2"
I have tried various ways of manipulating $cmd_options using single quotes and double quotes, but have been unsuccessful so far in getting it right. Any pointers at where I am going wrong?
When you build a command, delay the interpolation and use eval to execute it.
HOSTNAMES='host1, host2'
cmd_options=''
if [ "$HOSTNAMES" != "" ]; then
cmd_options+='--hostnames "$HOSTNAMES"'
fi
eval "prog $cmd_options"
A better solution is to use an array.
HOSTNAMES='host1, host2'
cmd_options=()
if [ "$HOSTNAMES" != "" ]; then
cmd_options+=(--hostnames "$HOSTNAMES")
fi
prog "${cmd_options[#]}"
If prog is the following program:
#!/usr/bin/perl
use feature qw( say );
say 0+#ARGV; say for #ARGV
Both snippets output the following:
2
--hostnames
host1, host2
Looks like you will not be able to embed a list inside $cmd_options,
as it prevents you from using the quotation-marks properly -
"escaping" the quotation-marks with the backslash (\")
converts them to a regular " character - not a delimiter, and as such,
they are simply concatenated to the first and last items of the $HOSTNAMES list.
Suggest you drop this line:
cmd_options+=" --hostnames \"$HOSTNAMES\""
and, instead, use the following two lines, as needed
(this assumes you still need $cmd_options for passing other parameters)
perl myscript.pl $cmd_options --hostnames "$HOSTNAMES"
perl myscript.pl $cmd_options
Wrapped in an if statement, it should look like this:
if [ "$HOSTNAMES" != "" ]
then
perl myscript.pl $cmd_options --hostnames "$HOSTNAMES"
else
perl myscript.pl $cmd_options
fi
Another option is to make sure there are no spaces in the $HOSTNAMES list -
it will allow to pass the list as a single parameter and the quotation-marks will not be required anymore.
Assuming you don't need the script's positional parameters any more, you can set them yourself. (This will work in any POSIX shell, where arrays are unavailable.)
# Save any positional parameters first, if necessary;
# we're going to overwrite them.
first_arg=$1
second_arg=$2
# etc.
set -- --hostnames "$HOSTNAMES"
perl myscript.pl "$#"

How do I test if a perl command embedded in a bash script returns true?

So, I have a bash script inside of which I'd like to have a conditional which depends on what a perl script returns. The idea behind my code is as follows:
for i in $(ls); do
if $(perl -e "if (\$i =~ /^.*(bleh|blah|bluh)/) {print 'true';}"); then
echo $i;
fi;
done
Currently, this always returns true, and when I tried it with [[]] around the if statement, I got errors. Any ideas anyone?
P.s. I know I can do this with grep, but it's just an example. I'd like to know how to have Bash use Perl output in general
P.p.s I know I can do this in two lines, setting the perl output to a variable and then testing for that variables value, but I'd rather avoid using that extra variable if possible. Seems wasteful.
If you use exit, you can just use an if directly. E.g.
if perl -e "exit 0 if (successful); exit 1"; then
echo $i;
fi;
0 is success, non-zero is failure, and 0 is the default if you don't call exit.
To answer your question, you want perl to exit 1 for failure and exit 0 for success. That being said, you're doing this the wrong way. Really. Also, don't parse the output of ls. You'll cause yourself many headaches.
for file in *; do
if [[ $file = *bl[eau]h ]]; then
echo "$file matches"
fi
done
for file in * ; do
perl -e "shift =~ /^.*(bleh|blah|bluh)/ || exit 1" "$file" && echo $file: true
done
You should never parse the output of ls. You will have, at least, problems with file names containing spaces. Plus, why bother when your shell can glob on its own?
Quoting $file when passing to the perl script avoids problems with spaces in file names (and other special characters). Internally I avoided expanding the bash $file variable so as to not run afoul of quoting problems if the file name contained ", ' or \
Perl seems to (for some reason) always return 0 if you don't exit with an explicit value, which seems weird to me. Since this is the case I test for failure inside the script and return nonzero in that case.
The return value of the previous command is stored in the bash variable $?. You can do something like:
perl someargs script.pl more args
if [ $? == 0 ] ; then
echo true
else
echo false
fi
It's a good question, my advice is: keep it simple and go Posix (avoid Bashisms1) where possible..
so ross$ if perl -e 'exit 0'; then echo Good; else echo Bad; fi
Good
so ross$ if perl -e 'exit 1'; then echo Good; else echo Bad; fi
Bad
1. Sure, the OP was tagged bash, but others may want to know the generic-Posix form.