resetting positional parameters and account for spaces in arguments - shell

To reset positional parameters such as $1 $2 $3 $4, one can run commands similar to what's shown below:
var1=first
var2=second
var3=third
set -- $var1 $var2 $var3
echo $var1
first
echo $var2
second
echo $var3
third
This is all fine and dandy. But suppose the arguments have spaces in them:
var1=first
var2="second 2 whatever"
var3=third
var4="now is good"
Is there a portable way to ensure after running set -- $var1 $var2 $var3 $var4 the quotes in var2 and var4 are preserved?
That way, when i run a echo $var2 i get second 2 whatever instead of "second

Related

How to make double quoted variable in bash to output empty instead of two single quote?

I got a bash script , for example
[root#test ~]# cat 1.sh
#!/bin/bash
var1=""
var2=""
touch "$var1"
touch $var2
it output as:
[root#test ~]# bash -x 1.sh
+ var1=
+ var2=
+ touch ''
touch: cannot touch ‘’: No such file or directory
+ touch
touch: missing file operand
so if I use "$var" over command , when it is empty , it will become '' and breaks my command
while if I use $var without double quotes , it works but shell check keep complaint to double quote to prevent word split
how can I workaround this ?
-------------edit -------
the touch here is example , please don't mind the command here , the goal is to make it output nothing
e.g.
my_command $var1 $var2 $var3 $var4
these var can be empty , so if I double quoted it
my_command "$var1" "$var2" "$var3" "$var4"
let's say if var1 and 2 is empty , then it will run as
var1=""
var2=""
var3="some"
var4="thing"
my_command "$var1" "$var2" "$var3" "$var4"
it output as
my_command '' '' some thing , and broken due to '' in argument.
if I do
var1=""
var2=""
var3="some"
var4="thing"
my_command $var1 $var2 $var3 $var4
it goes as my_command some thing which just works , but shell check keep complaints :(
You can omit empty variables from an argument list like this:
my_command ${var1:+"$var1"} ${var2:+"$var2"} ${var3:+"$var3"} ${var4:+"$var4"}
Explanation: ${var:+something} expands to something if var is defined and non-empty. So ${var:+"$var"} expands to a properly double-quoted reference to $var if the variable isn't empty. If it's empty or undefined, the whole thing expands to nothing, and since the outer reference isn't double-quoted it gets eliminated by word splitting.
When not quoted, empty string var=""; touch $var expands to nothing (as if you did not add it).
But when quoted var=""; touch "$var" then it expands to the empty string from var and this is an invalid filename argument to touch.
how can I workaround this ?
There is nothing to workaround as it works as expected. The presented command touch "$var" works as expected. When var is empty, one empty argument is being passed to touch command.
As creating a file with empty name is invalid, happily touch complains with a message.
the goal is to make it output nothing
Then check if the argument to touch is non-empty before running it.
if [ -n "$var" ]; then
somecommand "$var"
fi
If you want to omit an argument from a list if it's empty, use bash array to accumulate existing arguments and pass them properly quoted to the command:
args=()
if [[ -n "$var1" ]]; then
args+=("$var1")
fi
if [[ -n "$var2" ]]; then
args+=("$var2")
fi
somecommand "${args[#]}"
The POSIX compatible alternatives to bash arrays is to use set -- "$#" "$var" posititional arguments to accumulate the arguments or str+="$(printf " %q" "$var") properly quoted string to be evalulated.
If you want to "make it output nothing" literally, then silence stdout and stderr of a command, typically by redirecting to /dev/null:
{ somecommand ...; } >/dev/null 2>&1
You should check the validity of your variables before trying to use them. For example, you can use if [[ -z $VAR ]]:
if [[ -z $var1 || -z $var2 || -z $var3 || -z $var4 ]]; then
echo "Error: argument cannot be empty"
exit 1
fi
my_command "$var1" "$var2" "$var3" "$var4"
You can also split it into various ifs in case you want different messages for each one, for example.

Why $1 output is nothing?

I wrote a simple bash script and in the end of that I tried to test positional arguments like $0, $1, ...
echo please enter your name
read name
if [ -z "$name" ]
then
echo please enter your name
fi
if [ -n "$name" ]
then
echo Thank you so much
fi
echo $0
echo $1
echo $2
echo $3
After I run that, the output was:
please enter your name
j
Thank you so much
/bin/reza.sh
Why just $0 had output and other had nothing?
Run it like below
./bin/reza.sh first second third
please enter your name
monk
Thank you so much
/bin/reza.sh
first
second
third
Also, $0 is file name of the script itself.
The arguments you enter to a script are take in the order $1,$2,$3 and so on.
In this testscript:
#!/bin/bash
echo $0 #Gives you the command/script name itself
echo $1 #Gives you the first argument
echo $2 #Gives you the second argument
echo $3 #Gives you the third argument
echo $# #Gives you all arguments
echo $# #Gives you the total number of arguments excluding the script name
So the result of
$./testscript a b c
is
./testscript
a
b
c
a b c
3
If the argument is not assigned, its value is null or nothing will be printed.
$ printf "%sThere is nothing before this.\n" $1
gives you :
There is nothing before this.
Note: Don't use echo to test arguments, echo will append a newline at the end automatically as in bash.

Assign the output to variable in shell script

I am new to shell scripting and i am trying to assign the output of this line to a variable but all efforts are in vain.
var2=$(cat "filename")
var1=$(echo " $var2 +3 " | bc )
var2 is properly read from the file and also the output shows the value of the sumation, but the value is not assigned to the var1
ps : Filename contains a single entry which is a number
The code worked when i tried this
var3="$("echo " $var2 +3 " |bc)"
You are missing parenthesis
var2=$(cat "file")
var3=$(( var2 + 3 ))
echo $var3
Also you can try with expr
Something like this
var2=$(cat "filename")
var3=`expr $var2 + 3`
echo $var3
Here is a good answer
HIH

From bash script to bash script in infinite loop

A very simple example of the "just run once" version of my Script:
./myscript.sh var1 "var2 with spaces" var3
#!/bin/bash
echo $1 #output: var1
echo $2 #output: var2 with spaces
echo $3 #output: var3
Working as intended!
Now I try to just start the script and enter the vars in a loop, because later I want to copy multiple datasets at once to the shell.
./myscript.sh
#!/bin/bash
while true; do
read var1 var2 var3
#input: var1 "var2 with spaces" var3
echo $var1 #output: var1
echo $var2 #output: "var2
echo $var3 #output: with spaces" var3
done
It seems read splits the input at the spaces, putting all thats left in the last var, right? Is there any better possibility to add vars in a loop? Or how do I get read to behave like I added the vars just behind the script?
And what is the English word for that kind of loop to execute one script in a loop while copying different vars to the shell? Can't google for samples if I don't know what it is called...
This reads STDIN and parses those lines as arguments with shell quoting:
# Clean input of potentially dangerous characters. If your valid input
# is restrictive, this could instead strip everything that is invalid
# s/[^a-z0-9" ]//gi
sed -ue 's/[][(){}`;$]//g' | \
while read input; do
if [ "x$input" = "x" ]; then exit; fi
eval "set -- $input"
# check argument count
if [ $(( $# % 3 )) -ne 0 ]; then
echo "Please enter 3 values at a time"
continue;
fi
echo $1
echo $2
echo $3
done
set -- $input does all of the magic. See the Bash manual page for set.
--
If no arguments follow this option, then the positional parameters are
unset. Otherwise, the positional parameters are set to the arguments,
even if some of them begin with a ‘-’.

Bash: Spliting string based on some delimiter and storing each in a variable

113050050/CS101/mysql_java.pdf
the above is my string, which is stored in a variable 'line'
line="113050050/CS101/mysql_java.pdf"
Now I want to split $line based on delimiter / and store each single part in a variable
var1=113050050
var2=CS101
var3=mysql_java.pdf
$ IFS=/ read var1 var2 var3 <<< "$line"
Results
$ echo $var1
113050050
$ echo $var2
CS101
$ echo $var3
mysql_java.pdf
This might work for you:
line="113050050/CS101/mysql_java.pdf"
var=(${line//\// })
var1=${var[0]}
var2=${var[1]}
var3=${var[2]}
as #chepner points out this will fail if spaces exist in the $line variable, a perhaps more bullet-proof solution is to use the IFS variable:
line="113050050/CS101/mysql_java.pdf" O="$IFS" IFS='/' var=($line) IFS="$O"

Resources