So I was working on a project tonight and assumed based on my poor understanding that the requirement was to create a script to take a number and count down to 1 with commas on the same line.
A few people here introduced me to the seq command and I was on my way.
Turns out it needs to take the variable integer from a command line argument.
What I have now:
#!/bin/bash
#countdown
read -p "Enter a Number great than 1: " counter
seq -s, $counter -1
Needs to work by taking an argument after the line, such as /assign1p1 5 and then outputting 5,4,3,2,1
I've seen the $1 used as an argument marker? Is that how to work from it?
Use Three Arguments
The correct call to seq for your use case is:
seq [OPTION]... FIRST INCREMENT LAST
To decrement your starting value down to 1 using the defined separator, try something similar to this example:
$ set -- 5
$ seq -s, $1 -1 1
5,4,3,2,1
Obviously, the call to set won't be needed inside the script, but is a great way to test at the command line.
The command-line arguments passed to your script are $1, $2, etc.
#!/bin/bash
seq -s, $1 1
echo
If you want to make this more robust you might want to verify that the user passed in the correct number of arguments, which is the variable $#.
#!/bin/bash
if (( $# != 1 )); then
echo "Usage: $0 num" >&2
exit 1
fi
seq -s, $1 1
echo
Arguments passed to the script from the command line include : $0, $1, $2, $3 . . .
$0 is the name of the script itself, $1 is the first argument, $2 the second, $3 the third, and so forth. [2] After $9, the arguments must be enclosed in brackets, for example, ${10}, ${11}, ${12}.
If for whatever reason you do not want to use seq
a=$1
for (( b = a; b > 0; b-- ))
do
(( b == a )) || printf ,
printf $b
done
Related
$ bash argcnt.sh this is a "real live" test
is
real live
(to display only paired arguments)
Because, I know only in this way:
#!/bin/bash
echo "$2"
echo "$4"
It seems like you want to print every other argument given to the script. You could then create a loop over $#:
#!/bin/bash
# idx will be 2, 4, 6 ... for as long as it's less than the number of arguments given
for ((idx = 2; idx < ${##}; idx += 2))
do
# variable indirection below:
echo "${!idx}"
done
Note: You can use $# instead of ${##} to get the number of elements in $# too. I don't know which one that is preferred by people in general.
If what you want is print every other argument, starting from the second, you can use shift:
$ cat argcnt
#!/bin/bash
while shift; do printf '%s\n' "$1"; shift; done
$ ./argcnt this is a "real live" test foo
is
real live
foo
I have a script that takes in several arguments.
I need everything but $1 and $2 in a string.
I have tried this:
message="$*"
words= $(grep -v "$2"|"$3" $message)
but it doesn't work, it gives me the error:
./backup: line 26: First: command not found
Use shift 2 to shift the arguments along (it drops the first n arguments).
If you need "$1" and "$2" for later, save them in variables first.
Note that in shell, assignments to variables cannot have whitespace either side of the =.
First=$1
Second=$2
shift 2
Message=$#
Maybe something like this?
[root#tsekmanrhel771 ~]# cat ./skip1st2.sh
#!/bin/bash
COUNT=0
for ARG in "$#"
do
COUNT=$[COUNT + 1]
if [ ${COUNT} -gt 2 ]; then
RESULT="${RESULT} ${ARG}"
fi
done
echo ${RESULT}
[root#tsekmanrhel771 ~]# ./skip1st2.sh first second third 4 5 6 7
third 4 5 6 7
You can use a subarray:
$ set -- arg1 arg2 arg3 arg4
$ str=${*:3}
$ echo "$str"
arg3 arg4
More often than not, it's good practice to preserve the arguments as separate elements, though, which you can do by using $# and assigning to a new array:
$ arr=("${#:3}")
$ declare -p arr
declare -a arr=([0]="arg3" [1]="arg4")
Notice that in str=${*:3}, quoting isn't necessary, but in arr=("${#:3}"), it is (or the arguments would be split on whitespace).
As for your error message: your command
words= $(grep -v "$2"|"$3" $message)
does the following:
It sets a variable words to the empty string for the environment of the command (because there is a blank after =).
It tries to set up a pipeline consisting of two commands, grep -v "$2" and "$3" $message. The first of these commands would just hang and wait for input; the second one tries to run the contents of $3 as a command; presumably, based on your error message, $3 contains First.
If the pipeline would actually run, its output would be run as a command (again because of the blank to the right of =).
I've reviewing some bash scripts written by other people at work and I found this line that I'm trying to understand
[[ $(awk 'BEGIN{print ('$CAPACITY'>=0.9)}') -eq 1 ]] && echo "Capacity at 90 Percent"
Is my understanding that this line is replacing an if statement. Could someone help me out explaining what this line really does. Thanks
This makes me very sad and pessimistic about the future of civilization...
Let's break this down:
[[ $(awk 'BEGIN{print ('$CAPACITY'>=0.9)}') -eq 1 ]] && echo "Capacity at 90 Percent"
Note the $(....). This tells the shell to execute the program inside, and replace the contents of $(...) with the value. For example:
$ file_name="/usr/local/bin/foo"
$ short_name="$(basename $file_name)"
$ echo $short_name
foo
In this the second line, we are running the command basename $file_name. This returns foo. Then, the shell will substitute foo for $(basename $filename) before assigning short_name. Here's the same thing with the debugger on:
$ set -xv
$ file_name="/usr/local/bin/foo"
foo=/usr/local/bin/foo
+ foo=/usr/local/bin/foo
$ short_name=$(basename $file_name)
short_name=$(basename $file_name)
basename $file_name
++ basename /usr/local/bin/foo
+ short_name=foo
$ echo $short_name
echo $short_name
+ echo foo
foo
$ set +xv # Turn off the debugger.
You can see how the shell executes $(...) and replaces it.
Thus, the user is actually running the program:
awk 'BEGIN{print ('$CAPACITY'>=0.9)}'
However, take a look at the quotation marks:
awk 'BEGIN{print ('$CAPACITY'>=0.9)}'
+++++++++++++ +++++++
The stuff with the pluses under it are part of the awk command inside single quotes and thus cannot be interpolated by the shell. HOWEVER, $CAPACITY is not in quotes. In other words, the value of $CAPACITY replaces that variable before the awk command is executed. Thus, if $CAPACITY is .8, the awk command will become:
awk 'BEGIN{print ('.8'>=0.9)}`
That's the very first part of the explanation.
Now on to the next part. How much do you know about awk?
Awk is a programming language that's usually part of Unix/Linux distributions. Awk normally works on files and assumes a loop around the file with each line being read in and operated on. For example:
$ awk '{print $1}` foo.txt
Let's assume that each line in foo.txt consists of several fields that are separated by spaces. The file foo.txt is read in and each line is passed through to the awk program and the awk program will print out the first field of each line.
However, there is no file for awk to operate on. This developer is using the special patter BEGIN. This is executed before any lines are read in. Since there is no file for awk to process, and there is no actual awk program (only a BEGIN statement), awk will execute this statement (assuming capacity is at 80%:
.8 >= .9
Like in Shell and other programming languages. This statement will evaluate as true or false. In awk, if this statement is true, it will a non-zero value (we hope 1). If it is false, it will equal zero. In this case, it will equal false.
Awk returns (like Perl) the last value it executes. Thus, if the capacity is at 80%, the awk statement .8 >= .9 will be false. Awk will return a zero.
Now, the entire $([[ $(awk 'BEGIN{print ('$CAPACITY'>=0.9)}') will be replaced with 0. Your [[ ... ]] test now becomes:
[[ 0 -eq 1 ]] && echo "Capacity at 90 Percent"
Well, [[ 0 -eq 1 ]] is false.
Now the final part.
The two commands && and || are list operators. Their name comes from the C programming operators of the same name, and the way C short circuits tests. For example,
if ( ( bar > 20 ) && ( foo < 30 ) ) {
is a typical C if statement. with foo and bar being variables. I am asking if bar is greater than 20 AND if foo is less than 30 to do something.
C will first evaluate bar > 20 and decide whether it is true or false. If bar > 20 is false, there's is no need to test foo < 30 because no matter what the results are, the statement is still false. What if bar is indeed greater than 20? You have to run the next part of the if statement.
Imagine this:
if ( ( bar > 20 ) || ( foo < 30 ) ) {
This says if bar is greater than 20 OR foo < 30. In this case, C will evaluate whether bar is greater than 20. If it is, there is no need to test whether or not foo is less than 30. The statement will be true no matter what the value of foo is. What if bar isn't greater than 20? Then, I have to test the value of foo.
So, if I have && and the first statement is false, don't do the second statement (the entire expression is false anyway). If the first statement is true, I have to run the second statement (because I don't know whether or not that entire statement is true or not).
If I have ||, the complete opposite happens. If the first statement is true, don't do the second statement (because the entire expression is true). If that first statement is false, I have to run the second statement.
The gist of this is:
[ "$foo" = "$bar" ] && echo "Foo equals bar"
is the same as:
if [ "$foo" = "$bar" ]
then
echo "Foo equals bar"
fi
Because if $foo does equal $bar, I have to execute the second part of the statement!
And, this:
[ "$foo" = "$bar" ] || echo "Foo and Bar are not equal"
is the same as:
if [ "$foo" != "$bar" ]
then
echo "Foo and Bar are not equal"
fi
So, first the shell substitutes in the value of the shell variable $CAPACITY into your little awk script.
Next the awk script runs testing whether or not the substituted value of $CAPACITY is greater than or equal to 0.9. Since there is no actual awk program, awk doesn't attempt to read in from STDIN.
Next, awk will assign a zero or non-zero value to that boolean statement (depending whether or not it's true). Then, the awk program will exit with the evaluated value of that boolean statement.
The shell now substitutes that zero or non-zero value for that entire $(...) phrase. This is run through a test to see if it is or isn't equal to 1.
Finally if that test statement is equal to 1, the && will tell the shell to evaluate that echo statement. Thus, if the shell variable $CAPACITY is .9 or greater, that echo statement will print.
That's a lot of machinations just to compare .8 (or whatever the capacity is) with .9, so why did the developer do this?
Probably because BASH shell can only do integer arithmetic. Since $CAPACITY is less than one, you can't do this:
if [[ $CAPACITY -le .9 ]]
then
echo "Capacity is at 90%"
fi
Instead of using awk, I would probably have used bc:
OVER_CAPACITY=$(bc <<<"$CAPACITY >= .9")
if [[ ! $OVER_CAPACITY -eq $(true) ]]
then
echo "Capacity is over 90%"
else
echo "Every thing is okay"
fi
It would have been a few more lines, but I hope it makes things a bit easier to understand and make the file easier to maintain.
The complete line can be thought of as [[ if something is true ]] &&=then do another thing
To understand what is going on in this code, turn on your mental shell parser, and find the innermost construct that will produce output. in this case
awk 'BEGIN{print ('$CAPACITY'>=0.9)}'
execute that on a cmd-line by itself. Obviously the variable CAPACITY has to be set with a value.
Then you can use the shell debug/trace facility (set -vx) to see every thing executing
CAPACITY=0.95
set -vx
[[ $(awk 'BEGIN{print ('$CAPACITY'>=0.9)}') -eq 1 ]] && echo "Capacity at 90 Percent"
+ awk 'BEGIN{print (0.95>=0.9)}'
+ [[ 1 -eq 1 ]]
+ echo 'Capacity at 90 Percent'
Capacity at 90 Percent
IHTH
It's not, the [[ and ]] are an improvement upon the test builtin and the && is an AND
So, what this line is doing equivalent to:
if [[ $(awk 'BEGIN{print ('$CAPACITY'>=0.9)}') -eq 1 ]] ; then
echo "Capacity at 90 Percent"
fi
In effect, the line is saying TEST this condition AND do this other thing only if it's TRUE
Similarly, you could do [[ something_to_test ]] || do this if something_to_test is false
which means, TEST this condition OR do this other thing
These are bash shell one-line shortcuts.
You got a lot of good explanations, now rewrite the whole thing as:
awk -v cap="$CAPACITY" 'BEGIN{ if (cap>=0.9) print "Capacity at 90 Percent" }'
for clarity and simplicity.
[[ .... ]] construct returns true or false.
so the exp in [[ .... ]] must be a logic operation
I need to get three arguments by test.ksh script
as the following
./test.ksh 12 34 AN
is it possible to set the argument by counter for example ?
for get_arg 1 2 3
do
my_array[get_arg]=$$get_arg
print ${my_array[get_arg]}
done
in this example I want to get three arguments from the user by loop counter "$$get_arg"
in place of $1 $2 $3
is it possible? and how ?
my_array=("$#")
for i in 0 1 2
do
echo "${my_array[$i]}"
done
This assigns all the arguments to array my_array; the loop then selects the first three arguments for echoing.
If you're sure you want the first three arguments in the array, you could use:
my_array=("$1" "$2" "$3")
If you want the 3 arguments at positions 1, 2, 3 in the array (rather than 0, 1, 2), then use:
# One or the other but not both of the two assignments
my_array=("dummy" "$#")
my_array=("dummy" "$1" "$2" "$3")
for i in 1 2 3
do
echo "${my_array[$i]}"
done
bash has a special variable
$#
which contains the arguments of the script it currently executes. I think this is what your'e looking for:
for arg in $# ; do
# code
done
Edit:
My bad ksh:
for arg;do
print $arg
done
Original Post:
Use shift to iterate through shell script parameters:
# cat test.sh
#!/bin/bash
while [ "$1" != "" ]; do
echo $1
shift
done
test run:
# ./test.sh arg1 monkey arg3
arg1
monkey
arg3
source
Even you don't need in $#, this would work the same:
#!/bin/bash
i=0
for arg; do
my_array[i]="$arg"
echo "${my_array[i]}"
(( i++ ))
done
That is,
if in words is not present, the for command executes the commands
once for each positional parameter that is set, as if in $# had been
specified.
I want to parse the arguments given to a shell script by using a for-loop. Now, assuming I have 3 arguments, something like
for i in $1 $2 $3
should do the job, but I cannot predict the number of arguments, so I wanted use an RegEx for the range and $# as the number of the last argument. I don't know how to use these RegEx' in a for-loop, I tried something like
for i in $[1-$#]
which doesn't work. The loop only runs 1 time and 1-$# is being calculated, not used as a RegEx.
Basic
A for loop by default will loop over the command-line arguments if you don't specify the in clause:
for arg; do
echo "$arg"
done
If you want to be explicit you can get all of the arguments as "$#". The above loop is equivalent to:
for arg in "$#"; do
echo "$arg"
done
From the bash man page:
Special Parameters
$# — Expands to the positional parameters, starting from one. When the expansion occurs within
double quotes, each parameter expands to a separate word. That is, "$#" is equivalent to "$1" "$2" .... If the double-quoted expansion occurs within a word, the expansion of the first
parameter is joined with the beginning part of the original word, and the expansion of the
last parameter is joined with the last part of the original word. When there are no positional parameters, "$#" and $# expand to nothing (i.e., they are removed).
Advanced
For heavy-duty argument processing, getopt + shift is the way to go. getopt will pre-process the command-line to give the user some flexibility in how arguments are specified. For example, it will expand -xzf into -x -z -f. It adds a -- argument after all the flags which separates flags from file names; this lets you do run cat -- -my-file to display the contents of -my-file without barfing on the leading dash.
Try this boilerplate code on for size:
#!/bin/bash
eval set -- "$(getopt -o a:bch -l alpha:,bravo,charlie,help -n "$0" -- "$#")"
while [[ $1 != -- ]]; do
case "$1" in
-a|--alpha)
echo "--alpha $2"
shift 2
;;
-b|--bravo)
echo "--bravo"
shift
;;
-c|--charlie)
echo "--charlie"
shift
;;
-h|--help)
echo "Usage: $0 [-a ARG] [-b] [-c]" >&2
exit 1
;;
esac
done
shift
Notice that each option has a short a long equivalent, e.g. -a and --alpha. The -a flag takes an argument so it's specified as a: and alpha: in the getopt call, and has a shift 2 at the end of its case.
Another way to iterate over the arguments which is closer to what you were working toward would be something like:
for ((i=1; i<=$#; i++))
do
echo "${#:i:1}"
done
but the for arg syntax that John Kugelman showed is by far preferable. There are, however, times when array slicing is useful. Also, in this version, as in John's, the argument array is left intact. Using shift discards its elements.
You should note that what you were trying to do with square brackets is not a regular expression at all.
I suggest doing something else instead:
while [ -n "$1" ] ; do
# Do something with $1
shift
# Now whatever was in $2 is now in $1
done
The shift keyword moves the content of $2 into $1, $3 into $2, etc. pp.
Let's say the arguments where:
a b c d
After a shift, the arguments are now:
b c d
With the while loop, you can thus parse an arbitrary number of arguments and can even do things like:
while [ -n "$1" ] ; do
if [ "$1" = "-f" ] ; then
shift
if [ -n "$1" ] ; then
myfile="$1"
else
echo "-f needs an additional argument"
end
fi
shift
done
Imagine the arguments as being an array and $n being indexes into that array. shift removes the first element, so the index 1 now references the element that was at index 2 prior to shift. I hope you understand what I want to say.