I used a " if [ -z $var ] " in a script, but when I run it in aix ksh, the result is completely wrong and seems to have sytax error.
aixtest:/tmp# ls -a
. .. a.sh b.sh
aixtest:/tmp# ls -a | grep -i abc
aixtest:/tmp# all=`ls -a | grep -i abc`
aixtest:/tmp# echo $all
aixtest:/tmp# if [ -z $all ]; then echo "empty"; else echo "not empty"; fi;
ksh: test: 0403-004 Specify a parameter with this command.
not empty
aixtest:/tmp# if [ ! -z $all ]; then echo "not empty"; else echo "empty"; fi;
empty
aixtest:/tmp# oslevel -s
6100-08-03-1339
aixtest:/tmp# echo $SHELL
/usr/bin/ksh
The script above, $all should be empty, but the output is wrong and I got a error saying that "ksh: test: 0403-004 Specify a parameter with this command."
After that I run this script in a HMC shell, which is a Linux distribution, output is correct.
hscpe#IDCP780HMC1:~> ls -a
. .. a.sh b.sh
hscpe#IDCP780HMC1:~> ls -a | grep -i abc
hscpe#IDCP780HMC1:~> all=`ls -a | grep -i abc`
hscpe#IDCP780HMC1:~> echo $all
hscpe#IDCP780HMC1:~> if [ -z $all ]; then echo "empty"; else echo "not empty"; fi;
empty
hscpe#IDCP780HMC1:~> if [ ! -z $all ]; then echo "not empty"; else echo "empty"; fi;
empty
hscpe#IDCP780HMC1:~> uname -a
Linux IDCP780HMC1 2.6.32-358.23.2.79.hmc7_4p.x86_64 #1 SMP Wed Nov 12 12:50:34 CST 2014 x86_64 x86_64 x86_64 GNU/Linux
hscpe#IDCP780HMC1:~> echo $SHELL
/bin/hmcbash
So where is the problem? Did I misuse "if" syntax in aix ksh or it is a bug ?
In some shells you'd have to quote the variable. I presume that's what's happening here (though I've never used AIX ksh).
if [ -z "$all" ]; ...
It doesn't work because the shell interprets it as if [ -z ]. To make it work, you need to quote it.
This example is from AIX 7.1.
$ echo $SHELL
/usr/bin/ksh
$ all=""
Not quoted:
$ if [ -z $all ]; then echo "empty"; else echo "not empty"; fi;
ksh: test: argument expected
not empty
Quoted:
$ if [ -z "$all" ]; then echo "empty"; else echo "not empty"; fi;
empty
Related
I searched for this but haven't found an answer to this particular situation. I'm familiar with file tests in shells and with using the [[ ]] syntax to perform regex matching.
Is there a way to combine these two operations in a compound conditional that doesn't require multiple nested ifs?
So far I've tried the following (...and other much crazier variations):
if [ -e ~/.profile -a $0 =~ bash ]; then echo yes ; fi
if [ -e ~/.profile -a ( $0 =~ bash ) ]; then echo yes ; fi
if [ -e ~/.profile -a [ $0 =~ bash ] ]; then echo yes ; fi
if [[ -e ~/.profile -a $0 =~ bash ]]; then echo yes ; fi
if [[ ( -e ~/.profile ) -a ( $0 =~ bash ) ]]; then echo yes ; fi
if [ -e ~/.profile -a $0 =~ bash ]; then echo yes; fi
if [ -e ~/.profile -a $( [ $0 =~ bash ] ) ]; then echo yes; fi
if [ -e ~/.profile -a [[ $0 =~ bash ]] ]; then echo yes; fi
if [ -e ~/.profile -a $([[ $0 =~ bash ]]) ]; then echo yes; fi
-a is treated as an AND when using single brackets, eg:
$ [ 3 -gt 1 -a 2 -lt 3 ] && echo 'true'
true
For double brackets you want to use &&, eg:
$ [[ 3 -gt 1 && 2 -lt 3 ]] && echo 'true'
true
Alternatively you can && to separate tests regardless of whether you're using single or double brackets, eg:
$ [ 3 -gt 1 ] && [ 2 -lt 3 ] && echo 'true'
true
$ [[ 3 -gt 1 ]] && [[ 2 -lt 3 ]] && echo 'true'
true
$ [ 3 -gt 1 ] && [[ 2 -lt 3 ]] && echo 'true'
true
NOTE: same rules apply for -o vs || (aka OR)
Apparently, when you want to represent a LOGICAL AND between these two statements, you must use && instead of -a (which the shell interprets as "does this file exist" file test in double brackets). Also, for the regex to work, the statement must be within [[ ]]. What was unknown to me at the time is that even though -a changes its meaning in double brackets, the -e -w -r and other file tests don't change their functionality (e.g. it's the same for single or double brackets).
if [[ -w ~/.bash_profile && $0 =~ bash ]]; then ( echo 1 ; echo 2 ) >> .bash_profile
elif [[ -w ~/.profile && <someothercondition> ]]; then
( echo 3
echo 4
echo 5
) >> .profile
fi
I am still on the learning path of bash, shell, Linux, regex etc. Today I share this bash shell script that I have programmed and that I want to use as a "module" in future scripts. It thoroughly tests the validity of a set of command line parameters. I would like to ask the experienced adepts for advice and comments on how to better archive things in terms of syntax, different approaches or alternative commands. There were a lot that I tried differently, but I couldn't figure it out. Especially I don't like the 'case' structures. I would rather define a set of option letters like 'cmds=(h o g a m)' and then loop through that with 'for c in "${cmds[#]}"; do'. But that leads to the problem that I would have to use dynamic variable names, and I couldn't figure it out. Another problem is, that I am able to assign a boolean 'true' but I can't negate it with something like 'a=!a'. Etc. Any suggestions very welcome!
#!/bin/bash
# Usage: -p <path> -g <group> -o <owner> -m <mask> -h (help)
# Extensive option and parameter check:
expecting_option=true # alternatingly expecting option switch and parameter on command line
for i do # loop $i trough all parameters
# display help:
if [ $i = "-h" ]; then
echo "Usage:"
echo "-p <path> (default .)"
echo "-o <owner>"
echo "-g <group>"
echo "-m <permission mask> (000 - 777)"
exit
fi;
if [ "$expecting_option" = true ]; then # next arg supposed to be an option
if [[ "$i" =~ ^(.)(.*?)(.*?)$ ]]; then # retrieve up to 3 single characters
# Does it begin with '-' ?
if [ ${BASH_REMATCH[1]} != "-" ]; then
echo "ERROR: Option to begin with '-' expected at '"$i"'" >&2
exit
fi
# only one letter length for options
if [ -n "${BASH_REMATCH[3]}" ]; then
echo "ERROR: Invalid option '"$i"'. Use -h for help" >&2
exit
fi
switch=${BASH_REMATCH[2]} # save the current option switch
# has this option already been set?
# is option valid?
case $switch in
o) if [ $o ]; then
echo 'ERROR: duplicate option: -o' >&2
exit
fi;;
g) if [ $g ]; then
echo 'ERROR: duplicate option: -g' >&2
exit
fi;;
m) if [ $m ]; then
echo 'ERROR: duplicate option: -m' >&2
exit
fi;;
p) if [ $p ]; then
echo 'ERROR: duplicate option: -p' >&2
exit
fi;;
*) echo "ERROR: Invalid option '"$i"'. Use -h for help" >&2
exit;;
esac
fi
# next arg supposed to be the parameter
expecting_option=!true # it's not true, so it works. But is it 'false'?
else # this is supposed to be a parameter for the previous option switch
if [[ "$i" =~ ^\- ]]; then # starts with '-' ?
echo "ERROR: Parameter for "$switch" missing." >&2
exit
fi
case $switch in
o) # check if designated owner exists (=0):
if ! [ $(id -u "$i" > /dev/null 2>&1; echo $?) -eq 0 ]; then
echo "ERROR: user '"$i"' does not exist." >&2
exit
fi
o="$i";;
g) # check if designated group exists:
if [ -z $(getent group "$i") ]; then
echo "ERROR: group '"$i"' does not exist." >&2
exit
fi
g="$i";;
m) if ! [[ $i =~ ^[0-7][0-7][0-7]$ ]]; then
echo "ERROR: Invalid right mask '"$i"'" >&2
exit
fi
m="$i";;
p) # check if path exists
if ! [ -d "${i}" ]; then
echo "ERROR: Directory '"$i"' not found." >&2
exit
fi
p="$i";;
esac
expecting_option=true
fi
done
# last arg must be a parameter:
if [ "$expecting_option" != true ]; then
echo "ERROR: Parameter for "$switch" missing." >&2
exit
fi
# at least o, g or m must be specified:
if ! [ $g ] && ! [ $o ] && ! [ $m ] ; then
# this didn't work: if ! [ [ $g ] || [ $o ] || [ $m ] ] ; then
echo "Nothing to do. Specify at least owner, group or mask. Use -h for help."
exit
fi
# defaults: path = . owner = no change group = no change mask = no change
# set defaults:
[[ -z $p ]] && p="."
# All necessary options are given and checked:
# p defaults to . otherwise valid path
# if o is given, than the user exists
# if g is given, than the group exists
# if m is given, than the mask is valid
# at least one of o,g or m are given
# no option dupes
# no missing parameters
# ok, now let's do something:
# set group:owner + mask of whole directory tree:
if [ $g ] || [ $o ] ; then
[[ -n $g ]] && g=":"$g # use chown's column only if group change required, with or without owner
sudo find $p -type f -exec chown $o$g {} + &&
sudo find $p -type d -exec chown $o$g {} +
fi
if [ $m ]; then
sudo find $p -type f -exec chmod $m {} + &&
sudo find $p -type d -exec chmod $m {} +
fi
With 'getopts', as suggested by #Shawn in a comment, the parsing will become:
# Default values for options
g=
o=
P=.
m=
while getopts g:o:p:m: opt ; do
case "$opt" in
g) g=$OPTARG ;;
o) o=$OPTARG ;;
p) p=$OPTARG ;;
m) m=$OPTARG ;;
# Abort on illgal option
*) exit 2 ;;
esac
done
shift $((OPTIND-1))
# Rest of code goes here
Using ONLY the commands: echo, grep, sed
Argument $1 of the script is a code that has to be translated according to the table below and the translation sent to STDOUT. The code is a one to four digit code that starts in "A" and increments alphabetically to "ZZZZ". For example: A, B, .... Z, AA, AB,.....
Code - Translation
- A -> 1
- B to AA -> 2
- AB to AF -> 3
- AG to ZZZZ -> 4
For example if the script is called script.sh A the output would be 1. If the script is called script.sh ABC the output would be 4.
#!/bin/bash
echo $1 | grep -e '^A$'>/dev/null && echo 1 && exit
echo $1 | grep -e '^[^A]$' -e '^[A][A]$' >/dev/null && echo 2 && exit
echo $1 | grep -e '^[A][B-F]$' >/dev/null && echo 3 && exit
echo $1 | grep -e '^[A-Z]\{2,4\}$' >/dev/null && echo 4 && exit
echo 0
exit
#!/bin/bash
str=$1
if [[ -z "${str#A}" ]]
then
echo 1
elif [[ -z "${str#?}" || -z "${str#AA}" ]]
then
echo 2
elif [[ -z "${str/A[B-F]/}" ]]
then
echo 3
else
echo 4
fi
# just in case, so you can say you used them:
grep . < /dev/null | sed > /dev/null
EDIT: silly ., you should be ?.
EDIT2: Command [ is gone, non-command [[ to the rescue!
I'm trying to write a bash script that helps solving crosswords. For example, the question is "Alcoholic Drink in German". I already have a 'B' at the first place, an 'R' at the last place and two gaps in between. So a regex would be $B..R^
Since I live in Switzerland, I'd like to use the ngerman dictionary (DICT=/usr/share/dict/ngerman).
Here's how I'd do it directly on the shell:
grep -i '^B...$' /usr/share/dict/ngerman
That works perfectly, and the word 'Bier' appears among three others. Since this syntax is cumbersome, I'd like to write a little batch script, that allows me to enter it like this:
crosswords 'B..R'
Here's my approach:
#!/bin/bash
DICT=/usr/share/dict/ngerman
usage () {
progname=$(basename $0)
echo "usage: $progname regex"
}
if [ $# -le 0 ]; then
usage
exit 1
fi
regex="'^$1$'"
cmd="grep -i $regex $DICT"
echo $regex
echo $cmd
$($cmd) | while read word; do
echo "$word"
done
But nothing appears, it doesn't work. I also output the $regex and the $cmd variable for debugging reasons. Here's what comes out:
'^B..R$'
grep -i '^B..R$' /usr/share/dict/ngerman
That's exactly what I need. If I copy/paste the command above, it works perfectly. But if i call it with $($cmd), it fails.
What is wrong?
you do not need to put quotes around regex variable string. and $($cmd) should change to $cmd
so the correct code is :
#!/bin/bash
DICT=/usr/share/dict/ngerman
usage () {
progname=$(basename $0)
echo "usage: $progname regex"
}
if [ $# -le 0 ]; then
usage
exit 1
fi
regex="^$1$"
cmd="grep -i $regex $DICT"
echo $regex
echo $cmd
$cmd | while read word; do
echo "$word"
done
Change regex="^'$1$'" to regex="^$1$" and $($cmd) to $cmd
Here is a fixed version:
#!/bin/bash
DICT=/usr/share/dict/ngerman
usage () {
progname=$(basename "$0")
echo "usage: $progname regex"
}
if [ $# -le 0 ]; then
usage
exit 1
fi
regex="^$1$"
cmd="grep -i $regex $DICT"
echo "$regex"
echo "$cmd"
$cmd | while read -r word; do
echo "$word"
done
But this script has potential problems. For example try running it as ./script 'asdads * '. This will expand to all files in a directory and all of them are going to be passed to grep.
Here is a bit improved version of your code with correct quoting and also with bonus input validation:
#!/bin/bash
DICT=/usr/share/dict/ngerman
usage () {
progname=$(basename "$0")
echo "usage: $progname regex"
}
if [ $# -le 0 ]; then
usage
exit 1
fi
if ! [[ $1 =~ ^[a-zA-Z\.]+$ ]]; then
echo 'Wrong word. Please use only a-zA-Z characters and dots for unknown letters'
exit 1
fi
grep -i "^$1$" "$DICT" | while read -r word; do
echo "$word"
done
Oh, now I got it. When I do it manually, '' are expanded! Here's my test program in C (param-test.c):
#include <stdio.h>
int main(int argc, char *argv[]) {
puts(argv[1]);
return 0;
}
Then I call:
param-test 'foo'
And I see:
foo
That's the problem! grep doesn't really get 'B..R', but just B..R.
This is my code
if grep -q $lines scanHistory;then
echo -n ''
else
if grep -q $lines waiting;then
echo -n ''
else
Download $lines
echo "---$lines was download successfully"
fi
fi
my purpoes is if $line can't be found in both scanHistory and waiting, then run Download.
I had try to make This code more simplily, and write if as
if grep -qv $lines scanHistory && grep -qv $lines waiting; then
....
but a failured....
try:
if ! grep -q $lines scanHistory && ! grep -q $lines waiting; then ...
The initial attempt using grep -v failed because
grep -v succeeds if any line of the input does not match the pattern.
Maybe you want
if ! ( grep -q $lines scanHistory || grep -q $lines waiting ) ; then
....
The problem is that -v doesn't work the way you think it should
When you grep nonexistant file, you get 1, it didn't find it, but when you grep -v nonexistant file the return code is 0 because all the other lines in file DID negative-match 'nonexistant.
I hope this helps.
grep -q -e "$lines" scanHistory -e "$lines" waiting
if [ $? -ne 0 ] # Check if the above command is a success or not, if not run downlaod.
then
<run_Download>
fi
Using bash short circuiting operators && and ||:
grep -q $lines scanHistory ||
grep -q $lines waiting ||
Download $lines &&
echo "---$lines was download successfully"
It can be put on a single line, as bellow, but the above is more readable:
grep -q $lines scanHistory || grep -q $lines waiting || Download $lines && echo "---$lines was download successfully"