Validate the number of arguments passed in bash from read - bash

I have a question about validating user input regarding number of arguments passed by the user in a bash script.
For example, if I use:
if [[ $# -eq 2 ]]
then...
that will check if 2 arguments passed from the command line like this:
./somescript.sh arg1 arg2
but how to validate if user passed 2 arguments when asked?
For example:
echo "Type 2 names:"
read...
if [[ user passed more || less than 2 arguments]]
echo "incorrect number of names"
Now if I try to use $# -eq 2 it doesn't work.
What's the proper way to do it?

Use an array:
read -r -a array
if [[ "${#array[#]}" -eq 2 ]]; then ...
See output of:
declare -p array

Alternatively if your shell has no array like ksh or POSIX shell, you can populate the arguments from the read variable like this:
read -r reply
set -f # Disable globbing
set -- $reply # without quotes
if [ $# -eq 2 ]; then

Related

How to assign stdin arguments to variables in BASH from Pipe

What I'm trying:
I'm trying to assign args to a variable if it is from Pipe
Expected:
What should I do inside my script to assign the arguments to variables so that they look like this?
if [ -p /dev/stdin ]; then
option1="one"
option2="two"
option3="three"
echo "$option1" "$option2" "$option3"
else
echo "no input"
fi
Input: echo one two three | ./myscript
Output: one two three
Question Update:
I need all the arguments presented before the |(pipe) as just string input to my script.
It should not check for existence or execute the binary(here the binary is echo) present before the |(pipe).
Input: echo one two three | ./myscript
Output: echo one two
The words one, two, three in echo one two three | ./myscript are arguments of echo; but to ./myscript they are only input, not arguments.
Reading "arguments" from stdin
To read each word into its own variable use
if [ -p /dev/stdin ]; then
read -r option1 option2 option3
echo "$option1" "$option2" "$option3"
else
echo "no input"
fi
If you want to allow an arbitrary number of words, use an array.
read -ra myArray reads all words from a single line into an array.
mapfile -t myArray reads all lines into an array.
read -rd '' -a myArray reads all words from all lines into an array.
To access the words in the array, use ${myArray[0]}, ${myArray[1]}, ..., ${myArray[${#myArray[#]}-1]}.
Using actual arguments
Instead of parsing stdin it might be better to use actual arguments. To do so, execute your script like ./myscript one two three. Then access the arguments using positional parameters:
if [ $# = 0 ]; then
echo "no arguments"
else
echo "The arguments are $1 $2 $3 ..."
fi
For an arbitrary number of arguments check out shift and $#.
You should probably simply use xargs instead. But what you ask isn't hard to do per se.
if [ -p /dev/stdin ]; then
read -r option1 option2 option3
echo "$option1" "$option2" "$option3"
else
echo "no input"
fi
With xargs, this would look like
option1=$1
option2=$2
option3=$3
and then you'd just run it with
echo first second third |
xargs ./yourscript

check if bash script input variable is a letter

I want to write a short script to ssh to different servers depending on the argument I provide - if it is a number, I want the number to be a part of the server name, if it is a letter I want it connect to a certain server. So far I have this:
#!/bin/bash
if [ $1 -eq ^[1,3-5]$ ]; then
ssh -X servername1$1
elif [ $1=h ]; then
ssh -X servername2
fi
but it keeps complaining that integer expression is expected and always tries to connect to servername2, no matter whether I give it a number or a letter.
I tried quotation marks around the first and the second comparison, I tried double equality sign in the second comparison - all to no avail. How can I make this work?
[ $1 -eq ^[1,3-5]$ ] is not bash syntax.
You can write like that:
#!/bin/bash
if [[ $1 =~ ^[1,3-5]$ ]]; then
echo "$1 integer"
elif [ $1 = h ]; then
echo "$1 letter"
fi
Output:
[sahaquiel#sahaquiel-PC Stackoverflow]$ ./numorlet.sh 1
1 integer
[sahaquiel#sahaquiel-PC Stackoverflow]$ ./numorlet.sh 3
3 integer
[sahaquiel#sahaquiel-PC Stackoverflow]$ ./numorlet.sh 10
[sahaquiel#sahaquiel-PC Stackoverflow]$ ./numorlet.sh h
h letter
[sahaquiel#sahaquiel-PC Stackoverflow]$ ./numorlet.sh hhhh
[sahaquiel#sahaquiel-PC Stackoverflow]$
Also, tell me please, if the letter will be only 'h', or it can be any?
If any, use [[ instead of [ also in elif condition, with regex (I think ^[a-zA-Z]$ will work)
I suppose you have a server list where you are taking the argument $1 from.
If yes, you will have to do some changes in the code.
#!/bin/bash
if [[ $1 -eq ^[1,3-5]$ ]]; then
ssh -X servername1$1 < /dev/null
elif [[ $1==h ]]; then
ssh -X servername2 < /dev/null
fi
SSH takes input from stdin by adding input from /dev/null you are telling ssh to take input from that specific file.
Using [[...]] tells that if has an expression.
you can read more about the [[..]] in this link "Confused about operators"
Also $1=h is an assignment not a comparison; == is used for comparison.

Bash - not enough arguments

I have never used bash before but I am trying to understand this piece of code. The script is supposed to display all log in names, full names and their user-ids. However, whenever I run I can not get past the first if statement and if I delete the statement, it does not work.
#!/bin/bash
if [ $# -lt 1 ];
then
printf "Not enough arguments - %d\n" $#
exit 0
fi
typeset user=""
typeset name=""
typeset passwdEntry=""
while [ $# -ge 1 ];
do
user=$1
shift
name=""
passwdEntry=`grep -e ^$user /etc/passwd 2>/dev/null`
if [ $? -eq 0 ]; then
name=`echo $passwdEntry|awk -F ':' '{print $5}'`
fi
echo "$user $name"
done
$# means "the number of arguments to the current Bash program", and $1 means "the first argument to the current Bash program".
So your problem is that you're not passing any arguments to the program; for example, instead of something like this:
./foo.sh
you'll need to write something like this:
./foo.sh USERNAME
As you are new to Bash, I highly recommend skimming and bookmarking the Bash Reference Manual, http://www.gnu.org/software/bash/manual/bashref.html. It's all on a single page, so you can use your browser's "find in page" function (typically Ctrl+F) to search for things.

Add command arguments using inline if-statement in bash

I'd like to add an argument to a command in bash only if a variable evaluates to a certain value. For example this works:
test=1
if [ "${test}" == 1 ]; then
ls -la -R
else
ls -R
fi
The problem with this approach is that I have to duplicate ls -R both when test is 1 or if it's something else. I'd prefer if I could write this in one line instead such as this (pseudo code that doesn't work):
ls (if ${test} == 1 then -la) -R
I've tried the following but it doesn't work:
test=1
ls `if [ $test -eq 1 ]; then -la; fi` -R
This gives me the following error:
./test.sh: line 3: -la: command not found
A more idiomatic version of svlasov's answer:
ls $( (( test == 1 )) && printf %s '-la' ) -R
Since echo understands a few options itself, it's safer to use printf %s to make sure that the text to print is not mistaken for an option.
Note that the command substitution must not be quoted here - which is fine in the case at hand, but calls for a more robust approach in general - see below.
However, in general, the more robust approach is to build up arguments in an array and pass it as a whole:
# Build up array of arguments...
args=()
(( test == 1 )) && args+=( '-la' )
args+=( '-R' )
# ... and pass it to `ls`.
ls "${args[#]}"
Update: The OP asks how to conditionally add an additional, variable-based argument to yield ls -R -la "$PWD".
In that case, the array approach is a must: each argument must become its own array element, which is crucial for supporting arguments that may have embedded whitespace:
(( test == 1 )) && args+= ( '-la' "$PWD" ) # Add each argument as its own array element.
As for why your command,
ls `if [ $test -eq 1 ]; then -la; fi` -R
didn't work:
A command between backticks (or its modern, nestable equivalent, $(...)) - a so-called command substitution - is executed just like any other shell command (albeit in a sub-shell) and the whole construct is replaced with the command's stdout output.
Thus, your command tries to execute the string -la, which fails. To send it to stdout, as is needed here, you must use a command such as echo or printf.
Print the argument with echo:
test=1
ls `if [ $test -eq 1 ]; then echo "-la"; fi` -R
I can't say how acceptable this is, but:
test=1
ls ${test:+'-la'} -R
See https://stackoverflow.com/revisions/16753536/1 for a conditional truth table.
Another answer without using eval and using BASH arrays:
myls() { local arr=(ls); [[ $1 -eq 1 ]] && arr+=(-la); arr+=(-R); "${arr[#]}"; }
Use it as:
myls
myls "$test"
This script builds whole command in an array arr and preserves the original order of command options.

Using the value of a variable in the name of an argument

I'm writing a bash script which will input arguments, the command would look like this:
command -a -b -c file -d -e
I would like to detect a specific argument (-b) with its specific location ($1, $2, $3)
#! /bin/bash
counter=0
while [ counter -lt $# ]
do
if [ $($counter) == "-b" ]
then
found=$counter
fi
let counter+=1
done
The problem rises in $($counter). Is there a way to use $counter to call the value of an argument? For instance if counter=2, I would like to call the value of argument $2. $($counter) doesn't work.
You can accomplish this without getopts (which is still recommended, though) by reworking your loop.
counter=1
for i in "$#"; do
if [[ $i == -b ]]; then
break
fi
((counter+=1))
done
Simply iterate over the arguments directly, rather than iterating over the argument positions.
bash also does allow indirect parameter expansion, using the following syntax:
#! /bin/bash
counter=0
while [ counter -lt $# ]
do
if [ ${!counter} = "-b" ] # ${!x} uses the value of x as the parameter name
then
found=$counter
fi
let counter+=1
done

Resources