Increase value variable bash - bash

I want to increase the var upPCs by one every time I go through the if statement.
upPCs= 0;
if [ $? -eq 0 ]; then
$upPCs ++;
fi
This doesn't work.

Bash (and other shells) do not allow spaces around the equals sign. A semi-colon ; is used to separate commands on a line, so putting one at the end of the line is a waste (unless your really enjoy typing semi-colons...).
upPCs=0
Use (( )) for arithmetics:
if (( $? == 0 )); then
(( upPCs++ ))
fi
Finally, you should indent your code inside if (and while, for, etc) statements, because if you don't a kitten dies. Seriously, scripts can get unreadable quite quickly if you do not indent - it is worth it.
By the way, often you don't need to test $? explicitly. You don't show the command that you are testing the result of, but the principle is that if is true if the command is successful (exit code of zero is considered success). For example:
if some_command
then
(( upPCs++ ))
fi

Related

Verifying bash script inputs

I've just started using Linux as part of my computer science degree.
I'm writing some very simple Bash scripts and I've become a tad bit stuck.
I would like the script I'm attempting to write to be able to differentiate between "non valid inputs ie letters" from "valid inputs ie numbers from a specific range"
Currently the script "works" although I'm having troubles with another echo that I would like only to "echo" when the below line is "not true", is there a simple way to write this? I'm not specifically looking for efficient code, just code that I can learn from and understand at my amateur level.
So, long story short, is it possible to obtain information from the command line below, so that I can have a simple "not true" variable that I can use in another "else" or "elif" command?
For reference line 1 is to detect alphabetical inputs, and line 2 being the line of code I would like to write as "not true" for use in another part of my script.
let xx=$a+1-1 2>/dev/null; ret=$?
if [ $a -ge 7 ] && [ $a -le 70 ] && [ $xx -eq $xx ] && [ $ret -eq 0 ]
I'm not sure I'm explaining it very well, so any help would be appreciated. :)
Welcome to Stack Overflow. :)
Start by reading the docs. I don't mean that in any way to be mean - it's just the best way to go about this.
c.f. this manual
Then read through the BashFAQs
Also, this site is really your friend. Start by familiarizing yourself with how to ask a question well.
For your question, if I read it right:
typeset -i xx # accepts only digits now.
If the input is foo, the value defaults to 0, so now just check the range.
if (( xx >= 7 && xx <= 70 )); then : value is ok
else echo "Value must be a number from 7 to 70"
exit 1
fi
Good luck. :)
One problem with the "variable with integer attribute" is that it still doesn't protect you from invalid input:
$ declare -i aNumber
$ aNumber=1234X
bash: 1234X: value too great for base (error token is "1234X")
See 6.5 Shell Arithmetic for how bash interprets values to be numbers (scroll down to the paragraph starting with "Integer constants follow the C language definition")
In my experience, the best way to check for valid numeric input is with string-oriented pattern matching.
if [[ $1 =~ ^[+-]?[0-9]+$ ]]; then
echo "input $1 is an integer"
fi
In addition to extended regular expressions, bash's advanced pattern matching can also be used within [[...]]
if [[ $1 == ?([+-])+([0-9]) ]]; then
echo "input $1 is an integer"
fi
((...)) is preferred over let. See the let builtin
command for details.
Also the shellcheck wiki entry.

shell script : nested loop and continue

while [condition]
do
for [condition]
do
if [ "$x" > 3 ];then
break
fi
done
if [ "$x" > 3 ];then
continue
fi
done
In the above script I have to test "$x" > 3 twice. Actually the first time I test it, if it is true I want to escape the while loop and continue to the next while loop.
Is there any simpler way so I can use something like continue 2 to escape the outer loop?
"break" and "continue" are close relatives of "goto" and should generally be avoided as they introduce some nameless condition that causes a leap in the control flow of your program. If a condition exists that makes it necessary to jump to some other part of your program, the next person to read it will thank you for giving that condition a name so they don't have to figure it out!
In your case, your script can be written more succinctly as:
dataInRange=1
while [condition -a $dataInRange]
do
for [condition -a $dataInRange]
do
if [ "$x" > 3 ];then
dataInRange=0
fi
done
done

test command in unix doesn't print output

Why is there no output to the command test 3 -lt 6 in the unix terminal? Shouldn't test output a 0 or a 1?
I did a man test and it says
Exit with the status determined by EXPRESSION
The exit status is not printed, it is just returned. You can test it in if or while, for example
if test 3 -lt 6 ; then
echo ok
else
echo not ok
fi
Moreover, the exit code of the last expression is kept in $?:
test 3 -lt 6; echo $?
test returns an exit status, which is the one that indicates if the test was succesfull. You can get it by reading $? right after executing test or you can use it directly in a control structure, for example:
if test 3 -lt 6
do echo "everything is aaaaalright"
else echo "whaaat?"
fi
Every command in the shell has a return value, not just test. Return values are rairly printed, we test them directly, for example with an if or while statement, or maybe use it in a shortcut with && or ||. Sometimes we might store the return value, which is also available in the variable ?.
The test command is rather ancient. Since you have tagged bash then you might wish to consider using
if (( 3 < 6 )); then
for arithmetic tests, or
if [[ $name == "Fred Bloggs" ]] ; then
for textual or pattern tests.
These are generally more intuitive and less error prone. Like test, (( )) and [[ ]] are commands in their own right, and can be used without if or while. They return 0 (success) on true, and 1 (failure) on false.
By the way, when you do a man test you are actually looking at the doc for the test program. You might be better using man bash or help test.

Distributing lines of commands using bash script

I'm trying to implement trivial parallelization where lines of commands are distributed among separate processes. For that purpose I wrote this script that I named jobsel:
(only "#! /bin/bash" and help message is omitted)
slots=$1
sel=$2
[[ $slots -gt 0 ]] || die_usage
[[ $sel -lt $slots ]] || die_usage
i=0
while read line
do
(( i % slots == sel )) && eval $line
i=$(( i + 1 ))
done
# in case the last line does not end with EOL
if [[ $line != "" ]]; then
(( i % slots == sel )) && eval $line
i=$(( i + 1 ))
fi
I put eval because I couldn't use redirection or pipe in the commands without it.
When I run this like $HOME/util/jobsel 22 0 < cmds on a console emulator when cmds is a file that contains lines like echo 0 >> out with increasing numbers, it outputs, as expected, 0, 22, 44... in separate lines. Good so far.
So I put this to work. But when I ran this through secure shell, I ran it through at with backgrounding (ending each line with &). Then there is a problem. When I entered 8 lines, 21 processes started! ps -AFH printed processes with identical command and different pIDs. All work processes were at the same level, directly under init. My program does not create child processes anyway.
Puzzled, I tried the echo 0 >> out script through at then the output contained duplicate lines. Still finding it hard to believe, and thinking simultaneous appending might have caused the anomaly, I used other methods to confirm that some lines were run multiple times.
Moreover, there was no such anomaly when everything was run in terminal or when I created separate at job for each worker process.
But how can this happen? Is something wrong with my script? Does at/atd have some bug?
GNU parallel is what you are trying to implement.
Hint : it works great combined to xargs

How Can I Improve This Script?

I'm learning shell scripting, and am finding it hard finding a good way to learn. I have created a script below which lets the user search various different Internet engines through options. I would be really grateful if someone could look through this and point out what I'm doing wrong, how to improve it, etc.
#!/bin/bash
## Get user search-engine option
while getopts aegwy: OPTIONS ; do
case "$OPTIONS" in
a) ENGINE="http://www.amazon.com/s/ref=nb_sb_noss/?field-keywords";;
e) ENGINE="http://www.ebay.com/sch/i.html?_nkw";;
g) ENGINE="http://www.google.com/search?q";;
w) ENGINE="http://en.wikipedia.org/wiki/?search";;
y) ENGINE="http://www.youtube.com/results?search_query";;
?) ERRORS=true;;
esac
done &>/dev/null
## Ensure correct command usage
[ $# -ne 2 ] || [ $ERRORS ] && printf "USAGE: $(basename $0) [-a Amazon] [-e eBay] [-g Google] [-w Wikipedia] [-y YouTube] \"search query\"\n" && exit 1
## Ensure user is connected to the Internet
ping -c 1 209.85.147.103 &>/dev/null ; [ $? -eq 2 ] && printf "You are not connected to the Internet!\n" && exit 1
## Reformat the search query
QUERY=`printf "$2" | sed 's/ /+/g'`
## Execute the search and exit program
which open &>/dev/null ; [ $? -eq 0 ] && open "$ENGINE"="$QUERY" &>/dev/null && exit 0 || xdg-open "$ENGINE"="$QUERY" &>/dev/null && exit 0 || printf "Command failed!\n" && exit 1
Thanks in advance everyone, means a lot!
Best posted in codereviews, as indicated above, but here are some mostly stylistic comments. I should stress that the script is pretty much fine as-is; these are just minor improvements that I think will help make the code easier to read/maintain, more robust in a couple cases, etc.
You don't need to use all-caps for variable names just because environment variables are all-caps; shell variables and environment variables aren't the same thing.
Since your $OPTIONS variable only holds one option at a time, a singular name would be better (e.g. $option). Or you could go with $opt, which is somewhat traditional here.
The : in your getopts string (aegwy:) indicates that the -y option expects an argument, as in -ysomething instead of just -y by itself. Since you aren't doing anything with $OPTARG, I'm guessing that's not intentional.
As others have said, an if/then/elif/else would probably be clearer than the chain of && and ||.
The test [ $ERRORS ] is somewhat unclear because it can mean a lot of different things depending on the content of the $ERRORS parameter. A more explicit indication that you only care about whether or not it's set would be [ -n "$ERRORS" ].
Comparisons like [ -ne ] and friends are mostly holdovers from before the shell had built-in integer arithmetic; the more modern idiom would be (( $# != 2 )).
Your usage message implies that the -a, -e, -g, -w, and -y options take arguments of the form Amazon, eBay, Google, etc. It would be clearer what the actual syntax of the command is without those additions; you can include an extra paragraph in the help text listing what each option stands for.
As a rule, error messages should go to stderr instead of stdout (>&2).
It's fine to use basename $0 for consistency of output, but there's something to be said for leaving $0 alone as it will reflect however the user actually invoked the command. Something to consider.
Not much point in using printf if you're not using a format string; just use echo, which automatically appends the newline. Usage messages traditionally don't include quotation marks, either; it's up to the user to quote the arg or not depending on whether it's needed.
Checking a command for success is exactly how if works, so there's no need to do explicit checks of $? unless you really care about the exact exit value. In the case of your connectivity ping, you probably don't care about why it failed, only that it did:
if ! ping -c 1 209.85.147.103 >/dev/null; then
echo >&2 "$0: You are not connected to the Internet!"
exit 1
fi
Your search query reformat might need to do more than just turn spaces into plus signs; what if it has an ampersand? But if you're just doing the spaces-to-pluses thing, you could use bash parameter expansion do it without sed: QUERY="${QUERY// /+}"
If your program relies on open/xdg-open etc, you should probably check for its availability at the top; no sense doing anything else if you know you can't possibly perform the requested operation anyway. And you can use a variable so you don't wind up repeating yourself in multiple clauses:
open=
for cmd in open xdg-open; do
if type -p "$cmd" >/dev/null; then
open="$cmd"
break
fi
done
if [ -z "$open" ]; then
echo >&2 "$0: open command not found."
exit 1
fi
And then later you can finish up with just this one line:
"$open" "$ENGINE=$QUERY" &>/dev/null
http://linuxcommand.org/ is an excellent resource to improve your bash scripting skills.
http://tldp.org/LDP/abs/html/ is another great document.
Hope this helps.

Resources