Why is this Bash script not changing the string's value? - bash

I can't understand why userType is not changing.
I know for certain it's successfully reaching determineType, but it isn't changing the value to "bbb" when I try to print out userType later.
userType="aaa"
function determineType {
userType="bbb"
}
function checkUser {
cat users.csv | \
while read userLine; do
if [[ $userLine =~ .*$user.* ]]
then
determineType
echo "1"
fi
done
echo "0"
}

As soulseekah said in a comment, your while loop is executed in a subshell. Instead, do (and, as a benefit, you get rid of the useless use of cat):
userType="aaa"
determineType() {
userType="bbb"
}
checkUser() {
while read userLine; do
if [[ $userLine = *$user* ]]; then
determineType
return 1
fi
done < users.csv
return 0
}
Note. I also changed a few things:
got rid of the useless regexp since the same can be achieved with globbing,
used more common ways of defining functions in bash,
used return instead of echo for returning values: you'd run into the same problem again with an echo: you'd probably use your function checkUser in another subshell to obtain the value returned by the echo.

You are using a pipe, which launch the while .. do in a subshell.
Changing the value of a variable in a subshell won't affect the original variable
You should replace the:
function checkUser {
cat users.csv | \
while read userLine; do
if [[ $userLine =~ .*$user.* ]]
then
determineType
echo "1"
fi
done
echo "0"
}
with
function checkUser {
while read userLine; do
if [[ $userLine =~ .*$user.* ]]
then
determineType
echo "1"
fi
done < users.csv
echo "0"
}
(This also get rid of a Useless Use Of Cat)

Related

How to tokenise string and call a function on each token in bash?

I have a text file with comma delimiter like following
for example str_data.txt
aaa,111,bbb
ccc,222,ddd
eee,333,fff
I have a bash function to validate each token (i.e. if each token is following some rule or not based on that function will echo true or false. (can leave it like [[ XYZ == "$1"]] also, instead of returning echo) )
for example
function validate_token {
local _rule = XYZ
if [[ XYZ == "$1" ]]; then
echo "true"
else
echo "false"
fi
}
I want to write a bash script (one-liner or multi-line) to validate all these tokens separately (i.e. validate_token "aaa" then validate_token "111") and finally answer "true" or "false" based on ANDing of each token's results.
Would yo please try the following:
validate_token() {
local rule="???" # matches a three-chraracter string
if [[ $1 == $rule ]]; then
echo 1
else
echo 0
fi
}
final=1 # final result
while IFS=',' read -ra ary; do
for i in "${ary[#]}"; do
final=$(( final & $(validate_token "$i") ))
# take AND with the individual test result
done
done < "str_data.txt"
(( $final )) && echo "true" || echo "false"
I've also modified your function due to several reasons.
When defining a bash function, the form name() { .. } is preferred.
It is not recommended to start the user's variable name with an underscore.
You have localized it and don't have to care about the variable name
collision.
When evaluating the conditional expression by using == or = operator
within [[ .. ]], it will be better to place the pattern or rule to the right of the
operator.
It will be convenient to return 1 or 0 rather than true or false for further calculation.
Hope this helps.
You can try the below, reading line by line and storing the values into an array, then iterating the array calling the function for each value :
IFS=","
while read line
do
read -ra lineValues <<< "$line"
for value in "${lineValues[#]}"
do
validate_token "$value"
done
done < your_txt_file

Change variable named in argument to bash function [duplicate]

This question already has answers here:
Dynamic variable names in Bash
(19 answers)
How to use a variable's value as another variable's name in bash [duplicate]
(6 answers)
Closed 5 years ago.
In my bash scripts, I often prompt users for y/n answers. Since I often use this several times in a single script, I'd like to have a function that checks if the user input is some variant of Yes / No, and then cleans this answer to "y" or "n". Something like this:
yesno(){
temp=""
if [[ "$1" =~ ^([Yy](es|ES)?|[Nn][Oo]?)$ ]] ; then
temp=$(echo "$1" | tr '[:upper:]' '[:lower:]' | sed 's/es//g' | sed 's/no//g')
break
else
echo "$1 is not a valid answer."
fi
}
I then would like to use the function as follows:
while read -p "Do you want to do this? " confirm; do # Here the user types "YES"
yesno $confirm
done
if [[ $confirm == "y" ]]; then
[do something]
fi
Basically, I want to change the value of the first argument to the value of $confirm, so that when I exit the yesno function, $confirm is either "y" or "n".
I tried using set -- "$temp" within the yesnofunction, but I can't get it to work.
You could do it by outputting the new value and overwriting the variable in the caller.
yesno() {
if [[ "$1" =~ ^([Yy](es|ES)?|[Nn][Oo]?)$ ]] ; then
local answer=${1,,}
echo "${answer::1}"
else
echo "$1 is not a valid answer." >&2
echo "$1" # output the original value
return 1 # indicate failure in case the caller cares
fi
}
confirm=$(yesno "$confirm")
However, I'd recommend a more direct approach: have the function do the prompting and looping. Move all of that repeated logic inside. Then the call site is super simple.
confirm() {
local prompt=$1
local reply
while true; do
read -p "$prompt" reply
case ${reply,,} in
y*) return 0;;
n*) return 1;;
*) echo "$reply is not a valid answer." >&2;;
esac
done
}
if confirm "Do you want to do this? "; then
# Do it.
else
# Don't do it.
fi
(${reply,,} is a bash-ism that converts $reply to lowercase.)
You could use the nameref attribute of Bash (requires Bash 4.3 or newer) as follows:
#!/bin/bash
yesno () {
# Declare arg as reference to argument provided
declare -n arg=$1
local re1='(y)(es)?'
local re2='(n)o?'
# Set to empty and return if no regex matches
[[ ${arg,,} =~ $re1 ]] || [[ ${arg,,} =~ $re2 ]] || { arg= && return; }
# Assign "y" or "n" to reference
arg=${BASH_REMATCH[1]}
}
while read -p "Prompt: " confirm; do
yesno confirm
echo "$confirm"
done
A sample test run looks like this:
Prompt: YES
y
Prompt: nOoOoOo
n
Prompt: abc
Prompt:
The expressions are anchored at the start, so yessss etc. all count as well. If this is not desired, an end anchor ($) can be added.
If neither expression matches, the string is set to empty.

Getting piped data to functions

Example output
Say I have a function, a:
function a() {
read -r VALUE
if [[ -n "$VALUE" ]]; then # empty variable check
echo "$VALUE"
else
echo "Default value"
fi
}
So, to demonstrate piping to that function:
nick#nick-lt:~$ echo "Something" | a
Something
However, piping data to this function should be optional. So, this should also be valid. and give the following output:
nick#nick-lt:~$ a
Default value
However, the function hangs, as the read command waits for data from stdin.
What I've tried
Honestly not a lot, because I don't know much about this, and searching on Google returned very little.
Conceptually, I thought there might be a way to "push" an empty (or whitespace, whatever works) value to the stdin stream, so that even empty stdin at least has this value appended/prepended, triggering read and then simply trim off that first/last character. I didn't find a way to do this.
Question
How can I, if possible, make both of the above scenarios work for function a, so that piping is optional?
EDIT: Apologies, quickly written question. Should work properly now.
One way is to check whether standard input (fd 0) is a terminal. If so, don't read, because that will cause the user to have to enter something.
function a() {
value=""
if [ \! -t 0 ] ; then # read only if fd 0 is a pipe (not a tty)
read -r value
fi
if [ "$value" ] ; then # if nonempty, print it!
echo "$value"
else
echo "Default value"
fi
}
I checked this on cygwin: a prints "Default value" and echo 42 | a prints "42".
Two issues:
Syntactic, You need a space, before closing ]]
Algorithmic, You need the -n (non-zero length) variable test, not -z (zero length)
So:
if [[ -n "$VALUE" ]]; then
Or simply:
if [[ "$VALUE" ]]; then
As [[ is a shell builtin, you don't strictly need the double quotes:
if [[ $VALUE ]]; then
Also refrain from using all uppercases as variable name, as these are usually used for denoting environment variables, and your defined one might somehow overwrite already existing one. So use lowercase variable name:
if [[ $value ]]; then
unless you are export-ing your variable, and strictly need it to be uppercased, also make sure it is not overwriting any already existing one.
Also, i would add a timeout to read e.g. -t 5 for 5 seconds, and if no input is entered, print the default value. Also change the function name to something more meaningful.
Do:
function myfunc () {
read -rt5 value
if [[ "$value" ]]; then
echo "$value"
else
echo "Default value"
fi
}
Example:
$ function myfunc () { read -rt5 value; if [[ "$value" ]]; then echo "$value"; else echo "Default value"; fi ;}
$ myfunc
Default value
$ echo "something" | myfunc
something
$ myfunc
foobar
foobar

How to check if a value is in a list?

I've got a script with a variable taken from command line parameters. I want to check if its value is one of dev, beta or prod. I've got a following code snippet:
#!/usr/bin/env bash
ENV_NAME=$1
echo "env name = $ENV_NAME"
ENVIRONMENTS=('dev','beta','prod')
if [[ $ENVIRONMENTS =~ $ENV_NAME ]]; then
echo 'correct'
exit
else
echo 'incorrect'
exit
fi
When I run my script, it doesn't matter which parameters I pass: ./script.sh beta or ./script.sh or ./script.sh whatever, I always get correct echoed. What is wrong in my script?
for i in ${ENVIRONMENTS[#]}; do
if [[ $i = $ENV_NAME ]]; then
echo "correct"
exit
fi
done
echo 'incorrect'
exit
For using bash re:
ENV_NAME=dev
ENVIRONMENTS="dev|beta|prod"
[[ $ENV_NAME =~ ^($ENVIRONMENTS)$ ]] && echo ${BASH_REMATCH[1]}
dev
Everyone's given a good suggestion already however there's another way. It's more efficient than using regex and probably more efficient than using a loop especially when having more values. The only thing is that this requires Bash 4.0 or newer.
declare -A ENVIRONMENTS=([dev]=. [beta]=. [prod]=.)
if [[ -n ${ENVIRONMENTS["$ENV_NAME"]} ]]; then
...
Put it in a function
I like to hide ugly implementation details, especially when it comes to bash.
function catPipe() # concatenate all arguments with a pipe character
{
local IFS='|'
echo "$*"
}
function matchList()
{
local needle="$1"
shift
local stack=$(catPipe "$#")
[[ "$needle" =~ ^($stack)$ ]]
}
If you don't like having a subshell, use this:
function matchList()
{
local needle="$1"
shift
IFS='|' eval 'local stack="$*"'
[[ "$needle" =~ ^($stack)$ ]]
}
Inspired by konsolebox' solution, I use the ^($var)$ syntax to avoid partial matches. First it looked like this:
function matchList()
{
local needle="$1"
shift
local stack="$#"
[[ ${stack[#]} =~ "$needle" ]] # would allow partial machtes like 'eta'
}
The function can be called with either a string, an array, or single items:
variants='dev beta prod'
varArray=(dev beta prod)
matchList prop $variants && echo 'prop: match!'
matchList beta $variants && echo 'beta: match!'
matchList beta ${varArray[#]} && echo 'beta: match!'
matchList eta alpha beta gamma && echo 'eta: match!'
# Output:
beta: match!
beta: match!
Or for the original example:
matchList $ENV_NAME ${ENVIRONMENTS[#]} && echo 'correct'
case (partial solution)
Side note: I tried to come up with a solution using case, as it would naturally fit the requirements.
It works, but I couldn't figure out if/how I can use ENVIRONMENTS in the statment.
ENV_NAME='beta'
ENVIRONMENTS='dev|beta|prod'
case $ENV_NAME in (dev|beta|prod) echo 'correct' ;; (*) echo 'incorrect' ;; esac

Compare $1 with another string in bash

I've spent 2 hours with an if statement, that never works like I want:
#should return true
if [ "$1" == "355258054414904" ]; then
Here is the whole script:
#!/bin/bash
param=$1
INPUT=simu_900_imei_user_pass.csv
OLDIFS=$IFS
IFS=,
[ ! -f $INPUT ] && { echo "$INPUT ime not found"; exit 99; }
while read imei email pass
do
echo "First Parameter-IMEI: $1"
if [ "$1" == "355258054414904" ]; then
echo "GOOD"
fi
done < $INPUT
IFS=$OLDIFS
This is the output of the script:
First Parameter-IMEI: 355258054414904
First Parameter-IMEI: 355258054414904
First Parameter-IMEI: 355258054414904
I have seen a lot of pages about the subject, but I can't make it work :(
EDIT: I Join the content of csv for better understanding ! Tx for your help !
4790057be1803096,user1,pass1
355258054414904,juju,capp
4790057be1803096,user2,pass2
358854053154579,user3,pass3
The reason $1 does not match is because $1 means the first parameter given to the script on the command line, while you want it to match the first field read from the file. That value is in $imei.
You probably meant:
if [ "$imei" == "355258054414904" ]; then
echo "GOOD"
fi
Since it is inside the loop where you read input file line by line.
To check content of $1 use:
cat -vet <<< "$1"
UPDATE: To strip \r from $1 have this at top:
param=$(tr -d '\r' <<< "$1")
And then use "$param" in rest of your script.
To test string equality with [ you want to use a single '=' sign.

Resources