What is wrong in this if condition? - bash

This is my code
ports="161,123"
portsArr=$(echo "${ports}" | tr "," "\n")
for port in "${portsArr[#]}"
do
echo "${port}"
if [ "${port}" = "161" ]; then
echo "161";
fi
if [ "${port}" = "123" ]; then
echo "123";
fi
done
For some reason, the if conditions in this code is not working. Although, I'm getting expected results in the Line 5 echo command. Can somebody please explain what is wrong here?

To declare an array, you need some ( ):
portsArr=( $(echo "${ports}" | tr "," "\n") )
You should consider using bash's test : [[ ]]
[[
is a bash keyword similar to (but more powerful than) the [ command. See
http://mywiki.wooledge.org/BashFAQ/031 and
http://mywiki.wooledge.org/BashGuide/TestsAndConditionals
Unless you're writing for POSIX sh, we recommend [[.

Related

sh: Is it safe to use a variable as a command if the command contains only letters, number and underscores?

I'm writing a POSIX compliant script in dash so I am having to get creative with using fake arrays.
Contents of fake_array.sh
fake_array_job() {
array="$1"
job_name="$2"
comma_count="$(echo "$array" | grep -o -F ',' | wc -l)"
if [ "$comma_count" -lt '1' ]; then
echo 'You gave a fake array to fake_array_job that does not contain at least one comma. Exiting...'
exit
fi
array_count="$(( comma_count + 1 ))"
position=1
while [ "$position" -le "$array_count" ]; do
item="$(echo "$array" | cut -d ',' -f "$position")"
"$job_name" || exit
position="$(( position + 1 ))"
done
}
Contents of script.sh
#!/bin/sh
. fake_array.sh
job_to_do() {
echo "$item"
}
fake_array_job 'goat,pig,sheep' 'job_to_do'
second_job() {
echo "$item"
}
fake_array_job 'apple,orange' 'second_job'
I am aware that it may seem silly to use a unique name for each job I pass to fake_array_job, but I like that I have to type it twice because it helps to reduce human error.
I keep reading that it is a bad idea to use a variable as a command. Does my use of "$job_name" to run a function have any negative implications as it concerns stability, security or efficiency?
(Read to the end for a good suggestion by Charles Duffy. I'm too lazy to completely rewrite my answer to mention it earlier...)
You can iterate over the "array" using simple parameter expansions without requiring multiple elements in the array.
fake_array_job() {
args=${1%,}, # Ensure the array ends with a comma
job_name=$2
while [ -n "$args" ]; do
item=${args%%,*}
"$job_name" || exit
args=${args#*,}
done
}
One problem with the above is that assures that the array is comma-terminated by assuming that foo,bar, is not a comma-delimited array with an empty last element. A better (though uglier) solution is to use read to break up the array.
fake_array_job () {
args=$1
job_name=$2
rest=$args
while [ -n "$rest" ]; do
IFS=, read -r item rest <<EOF
$rest
EOF
"$job_name" || exit
done
}
(You can use <<-EOF and make sure the here doc is indented with tabs, but it's hard to convey that here, so I'll just leave the ugly version.)
There's also Charles Duffy's good suggestion of using case to pattern match on the array to see if there are any commas left or not:
while [ -n "$args" ]; do
case $var in
*,*) next=${args%%,*}; var=${args#*,}; "$cmd" "$next";;
*) "$cmd" "$var"; break;;
esac;
done

How can I make bash evaluate IF[[ ]] from string?

I am trying to create a "Lambda" style WHERE script.
I want lambdaWHERE to take piped input and pass it through if condition after given as arguments is met. Like xargs I use {} to represent what comes down the pipe.
I call command like:
ls -d EqAAL* | lambdaWHERE.sh -f {}/INFO_ACTIVETICK
I want the folder names passed through if they contain a file called INFO_ACTIVETICK
Here is the script:
#!/bin/sh
#set -x
ARGS=$*
while read i
do
CMD=`echo $ARGS | sed 's/{}/'$i'/g'`
if [[ $CMD ]]
then
echo $i
fi
done
But when I run it a mysterious "-n" appears...
$ ls -d EqAAL* | /q/lambdaWHERE.sh -f {}/INFO_ACTIVETICK
+ ARGS='-f {}/INFO_ACTIVETICK'
+ read i
++ echo -f '{}/INFO_ACTIVETICK'
++ sed 's/{}/EqAAL-1m/g'
+ CMD='-f EqAAL-1m/INFO_ACTIVETICK'
+ [[ -n -f EqAAL-1m/INFO_ACTIVETICK ]]
+ echo EqAAL-1m
EqAAL-1m
+ read i
How can I get the bit in the [[ ]] correct?
You were quite close. you only need to switch to the standard POSIX [ $CMD ] and it will work.
The main difference between using [[ $CMD ]] and [ $CMD ] is that the first has fewer surprises and you need not quote variables. That also means that a variable is though of as one token and cannot have a whole expression in it like you are trying. [ $CMD ] however works the same way as the original shell where [ was just a command an thus need explicit quotations in order to interpret something with spaces as one argument.
There is a relevant question about the differences between [[ ...]] and [ ..]

shell script works but drop error "line[8] expected argument ["

I have shell script that works (does what i want to do,finds if listed user is online), but each time drop error "line[8] expected argument [". I've tried using == but same thing. There's my code:
#!/bin/sh
truth=0;
until [ $truth -eq 1 ]
do
for i; do
isthere=$(who is here | awk '{print $1}' | grep $i)
if [ $isthere = $i ] #(8 line is here)
then
echo "found user: "$isthere". program now will close.";
exit 0;
fi
done
echo "user not found, retrying after 3sec...";
sleep 3;
done
Thank you for you help and time.
Looks like $isthere or $i is empty. You should quote them: if [ "$isthere" = "$i" ]
In other news: most semicolons are useless; a semicolon it is not a statement terminator, but a statement separator, along with newline.

[: -gt: unary operator expected

I don't write a lot of Bash, so I'm a bit stumped as to how to fix this. I need to check whether a value returned from a command is greater than x. When it runs though I get [: -gt: unary operator expected which I'm unable to fix.
Here is my script,
#!/bin/sh
ERROR=0
PHPCPDLevel=100
# PHPCPD
echo "PHP CopyPaste Detection (Limit is at least ${PHPCPDLevel}%"
PHPCPD="phpcpd ."
if [[ `echo $PHPCPD | grep "%" | cut -d'.' -f1` -gt "$PHPCPDLevel" ]]
then
echo $PHPCPD
ERROR=1
else
echo "... -> Only `echo $PHPCPD | grep "%" | cut -d'.' -f1`%"
fi
echo "Finished!"
exit $ERROR
Update:
I think I've done it:
#!/bin/sh
ERROR=0
PHPCPDLevel=25
# PHPCPD
echo "PHP CopyPaste Detection (Limit is at most ${PHPCPDLevel}%)"
PHPCPD="phpcpd ."
PERCENTAGE=$($PHPCPD | grep "%" | cut -d'.' -f1)
if [ ${PERCENTAGE} -gt ${PHPCPDLevel} ]
then
echo $PHPCPD
ERROR=1
else
echo "Only $PERCENTAGE%"
fi
exit $ERROR
Remember that [ is a command. It maybe built into your shell, but it's still a command. It is expecting a particular set of parameters, and will give you an error when it gets something it doesn't understand. In fact, you can replace [ ... ] with test ... if that makes things a bit easier to understand:
For example:
test -gt 34
Will return:
bash: test: -gt: unary operator expected
Hmmm... same error message.
When you get things like this, you should use set -xv and set +xv around the problem area of your shell script. The set -xv will print out the shell command to be executed, and then will show you what the command line looked like after it has been mangled I mean interpolated by the shell.
I suspect that your error is:
if [ ${PERCENTAGE} -gt ${PHPCPDLevel} ]
That ${PERCENTAGE} is a blank value. If you use [[ ... ]] instead of [ ... ] you won't get that error. The [[ ... ]] is parsed a bit differently than [ ... ] because it's a compound command. The shell interpolations are done after the initial command is parsed, so it's a bit more forgiving if you miss a quotation mark or strings contain unexpected characters.
So:
ERROR=0
PHPCPDLevel=25
# PHPCPD
echo "PHP CopyPaste Detection (Limit is at most ${PHPCPDLevel}%)"
export PS4="\$LINENO: " # Prints out the line number being executed by debug
set -xv # Turn on debugging
PHPCPD="phpcpd ."
PERCENTAGE=$($PHPCPD | grep "%" | cut -d'.' -f1)
if [[ ${PERCENTAGE} -gt ${PHPCPDLevel} ]] # Use [[ ... ]] instead of [ .. ]
then
echo $PHPCPD
ERROR=1
else
echo "Only $PERCENTAGE%"
fi
set +xv # Turn off debugging
exit $ERROR
Now, you'll see what the various commands that set environment variables are returning, and possibly see something you didn't quite expect.
You can't use double brackets [[ ... ]] in sh. Change your sheebang to
#!/bin/bash
or change the syntax to use single brackets [ ... ]. Don't forget to quote the terms inside the expression if you do that.

How to get first character of variable

I'm trying to get the first character of a variable, but I'm getting a Bad substitution error. Can anyone help me fix it?
code is:
while IFS=$'\n' read line
do
if [ ! ${line:0:1} == "#"] # Error on this line
then
eval echo "$line"
eval createSymlink $line
fi
done < /some/file.txt
Am I doing something wrong or is there a better way of doing this?
-- EDIT --
As requested - here's some sample input which is stored in /some/file.txt
$MOZ_HOME/mobile/android/chrome/content/browser.js
$MOZ_HOME/mobile/android/locales/en-US/chrome/browser.properties
$MOZ_HOME/mobile/android/components/ContentPermissionPrompt.js
To get the first character of a variable you need to say:
v="hello"
$ echo "${v:0:1}"
h
However, your code has a syntax error:
[ ! ${line:0:1} == "#"]
# ^-- missing space
So this can do the trick:
$ a="123456"
$ [ ! "${a:0:1}" == "#" ] && echo "doesnt start with #"
doesnt start with #
$ a="#123456"
$ [ ! "${a:0:1}" == "#" ] && echo "doesnt start with #"
$
Also it can be done like this:
$ a="#123456"
$ [ "$(expr substr $a 1 1)" != "#" ] && echo "does not start with #"
$
$ a="123456"
$ [ "$(expr substr $a 1 1)" != "#" ] && echo "does not start with #"
does not start with #
Update
Based on your update, this works to me:
while IFS=$'\n' read line
do
echo $line
if [ ! "${line:0:1}" == "#" ] # Error on this line
then
eval echo "$line"
eval createSymlink $line
fi
done < file
Adding the missing space (as suggested in fedorqui's answer ;) ) works for me.
An alternative method/syntax
Here's what I would do in Bash if I want to check the first character of a string
if [[ $line != "#"* ]]
On the right hand side of ==, the quoted part is treated literally whereas * is a wildcard for any sequence of character.
For more information, see the last part of Conditional Constructs of Bash reference manual:
When the ‘==’ and ‘!=’ operators are used, the string to the right of the operator is considered a pattern and matched according to the rules described below in Pattern Matching
Checking that you're using the right shell
If you are getting errors such as "Bad substitution error" and "[[: not found" (see comment) even though your syntax is fine (and works fine for others), it might indicate that you are using the wrong shell (i.e. not Bash).
So to make sure you are using Bash to run the script, either
make the script executable and use an appropriate shebang e.g. #!/bin/bash
or execute it via bash my_script
Also note that sh is not necessarily bash, sometimes it can be dash (e.g. in Ubuntu) or just plain ol' Bourne shell.
Try this:
while IFS=$'\n' read line
do
if ! [ "${line:0:1}" = "#" ]; then
eval echo "$line"
eval createSymlink $line
fi
done < /some/file.txt
or you can use the following for your if syntax:
if [[ ! ${line:0:1} == "#" ]]; then
TIMTOWTDI ^^
while IFS='' read -r line
do
case "${line}" in
"#"*) echo "${line}"
;;
*) createSymlink ${line}
;;
esac
done < /some/file.txt
Note: I dropped the eval, which could be needed in some (rare!) cases (and are dangerous usually).
Note2: I added a "safer" IFS & read (-r, raw) but you can revert to your own if it is better suited. Note that it still reads line by line.
Note3: I took the habit of using always ${var} instead of $var ... works for me (easy to find out vars in complex text, and easy to see where they begin and end at all times) but not necessary here.
Note4: you can also change the test to : *"#"*) if some of the (comments?) lines can have spaces or tabs before the '#' (and none of the symlink lines does contain a '#')

Resources