Using a pipe inside a for loop. How do I preserve the value of the variable inside the for loop - bash

I've been stuck for quite some time with the following code. It works but the variable loses the value that was set during the iterations.
I have the following code
mistakes=0
entered_chars=()
word_length=0
answer=""
answer_guess=""
checkIfLetterInsideWord(){
exists=0
letter=$2
word_array=`echo $1 | grep -o . `;
for (( i=1; i <= $word_length; i++))
do
if [[ "${1:$i-1:1}" = ${letter} ]]; then
exists=1
answer_guess=$(echo $answer_guess | sed "s/-/${letter}/{i}" )
fi
done
echo $exists
}
askUserInput(){
answer=$answer
echo $answer
echo "Please type a letter"
read user_input
if [ ! -z $user_input ]; then
user_input=$(echo $user_input | tr '[:upper:]' '[:lower:]')
if [ $(checkIfAlreadyEntered "$user_input") -eq 0 ]; then
if [ $(checkIfLetterInsideWord $answer $user_input) -eq 0 ]; then
mistakes=$((mistakes + 1)); fi
echo "Current mistake count; $mistakes "
entered_chars+=($user_input)
else
echo "Char has already been entered"
fi
else
echo "You haven't entered any input!"
fi
}
guessTheWord() {
answer=$OPTARG
word_length=$(printf $answer | wc -m)
temp=$(echo $answer | sed 's/\(.\)/\1 /g')
array=($temp)
echo "The chosen word is $word_length long"
gameOngoing=true
for(( i=1; i<=$word_length; i++)) do
answer_guess="$answer_guess-"
done
while $gameOngoing
do
echo $answer_guess
askUserInput $answer
done
}
I want to preserve the value of the variable answer_guess. I understand that it loses the value because of the usage of a pipeline inside the loop but I don't know to approach this problem.

The problem has nothing do to with the pipe. Rather, it is that you call checkIfLetterInsideWord inside a command-substitution ($(...)). Command substitution executes in a subshell so environment changes in the function will not persist.
It would be better to rewrite checkIfLetterInsideWord so that it returns an exit status. Something like:
if [[ $exists ]]; then
return 0 # Success
else
return 1 # Failure
end
Then you could simply call it without worrying about a subshell:
if checkIfLetterInsideWord "$answer" "$user_input"; then
# letter is in word
else
# letter is not in word
fi
There are other issues with the code. I've limited this answer to the question about preserving the value of variables.

answer_guess=$(echo $answer_guess | sed "s/-/${letter}/{i}" )
replace the - with .
so your code becomes
answer_guess=$(echo $answer_guess | sed "s/./${letter}/{i}" )

Related

Checking if the string is in proper format in Shell Scripting

I am trying to check if the string is in format in shell script.
below is the code i am trying and the output i want to get.
Format: <datatype>(length,length) | <datatype>(length,length)
I have multiple cases with this scenario, if both datatypes have () then it should show pass, else fail.
Eg. decimal(1,0)|number(11,0) this should pass but int|number(11,0) or decimal(1,0)|int should fail.
Code1:
INPUT='decimal(1,0)|number(11,0)'
sub="[A-Z][a-z]['!##$ %^&*()_+'][0-9][|][A-Z][a-z]['!##$ %^&*()_+'][0-9][|]"
if [ "$INPUT" == "$sub" ]; then
echo "Passed"
else
echo "No"
fi
Code 2:
INPUT='decimal(1,0)|number(11,0)'
sub="decimal"
if [ "$INPUT" == *"("*") |"*"("*") " ]; then
echo "Passed"
else
echo "No"
fi
Any help will be fine. Also note, I am very new to shell scripting.
Reading both values into variables, first removing alpha characters and then checking variables are not empty
result='FAIL'
input='int|number(6,10)'
IFS="|" read val1 val2 <<<"$(tr -d '[:alpha:]' <<<"$input")"
if [ -n "$val1" ] && [ -n "$val2" ]; then
result='PASS'
fi
echo "$result: val1='$val1' val2='$val2'"
Result:
FAIL: val1='' val2='(6,10)'
For input='decimal(8,9)|number(6,10)'
PASS: val1='(8,9)' val2='(6,10)'
That looks like just a simple regex to write.
INPUT='decimal(1,0)|number(11,0)'
if printf "%s" "$INPUT" | grep -qEx '[a-z]+\([0-9]+,[0-9]+\)(\|[a-z]+\([0-9]+,[0-9]+\))*'; then
echo "Passed"
else
echo "No"
fi

How to retain the value of a variable inside for loop in shell script

Below is my shell script code:
#!/bin/bash
first=0
my_for_loop()
{
first="$1";
if [ $first -eq 0 ]
then
return 1;
elif [ $first -eq 1 ]
then
return 0;
fi
}
while read line; do
for word in $line; do
echo "word = '$word'"; ( echo "$word" | grep -Eq "\([0-9]+\)" ) && ( word="${word%\)}"; word="`echo ${word} | cut -d'(' -f 2`"; echo "no = $word";
my_for_loop "$first"; first="$?"; echo "foo = $first")
done
echo "first value is $first" )
done
Basically I'm trying to read the words from the input and checking whether it is of the form "([0-9]+)" {e.g. (120) } if so, extracting the numerals i.e 120
The problem arises when I'm trying to set the variable "first" to 1 on the odd no of occurence of the pattern, and to 0 on eventh occurence
I tried Process Substitution, but not working porperly.
Can you help me with setting the boolean value inside the for loop such that it retains the value during next iteration also?
Avoid subprocesses. Is this want you want:
i=0
while read line ; do
cutted=$(sed 's/^(\([0-9]*\))$/\1/' <<< $line)
if [ "${line}" != "${cutted}" ]; then
(( i = !i))
echo "First=$i and ${cutted}"
fi
done
and use another loop when you cut things in words
i=0
while read line ; do
for word in $line; do
echo "${word}"
cutted=$(sed 's/^(\([0-9]*\))$/\1/' <<< ${word})
if [ "${word}" != "${cutted}" ]; then
(( i = !i))
echo "First=$i and ${cutted}"
fi
done
done

Having difficulty writing a script to sort words

I am dealing with sorting words in Bash according to a given argument. I am given either argument -r, -a , -v or -h and according to it there are options to sort the words, as you can see at my "help".
Somehow, if I pass the argument -r it creates an error. I really don't understand what I am doing wrong, as if[["$arg"=="-a"]] works, but I have to use case somehow.
Here is my code:
#!/bin/bash
# Natalie Zubkova , zubkonat
# zubkonat#cvut.fel.cz , LS
#help
help="This script will calculate occurances of words in a given file, and it will sort them according to the given argument in following order> \n
without parametre = increasing order according to a number of occurance\n
-r = decreasing order according to a number of occurance\n
-a = in alphabetical increasing order\n
-a -r = in alphabetical decreasing order\n
There are also special cases of the given parametre, when the script is not sorting but:\n
-h = for obtaining help \n
-v = for obtaining a number of this task "
# this function will divide a given chain into a words, so we can start calculating the occurances, we also convert all the capital letters to the small ones by - tr
a=0;
r=0;
EXT=0;
if [ "$1" == "-h" ]; then
echo $help
exit 0
fi
if [ "$2" == "-h" ]; then
echo $help
exit 0
fi
if [ "$1" == "-v" ]; then
echo "5"
exit 0
fi
if [ "$2" == "-v" ]; then
echo "5"
exit 0
fi
function swap {
while read x y; do
echo "$y" "$x";
done
}
function clearAll {
sed -e 's/[^a-z]/\n/gI' | tr '[A-Z]' '[a-z]' | sort | uniq -c |awk '{i++; if(i!=1) print $2" "$1}' #swap
}
for arg do
case "$arg" in
"-a")
a=1
;;
"-r")
r=1
;;
"-v")
echo "5" #number of task is 5
exit 0
;;
"-h")
echo $help
exit 0
;;
"-?")
echo "invalid parametre, please display a help using argument h"
exit 0
;;
esac
done
#Sort according to parametres -a and -r
function sortWords {
if [[ a -eq 1 ]]; then
if [[ r -eq 0 ]]; then
clearAll | sort -nk1
fi
fi
if [[ a -eq 1 ]]; then
if [[ r -eq 1 ]]; then
clearAll | sort -nk1 -r
fi
fi
if [[ r -eq 1 ]]; then
if [[ a -eq 0 ]]; then
clearAll | sort -nk2 -r
fi
fi
if [[ a -eq 0 ]]; then
if [[ r -eq 0 ]]; then
clearAll | sort -nk2
fi
fi
}
#code is from Stackoverflow.com
function cat-all {
while IFS= read -r file
do
if [[ ! -z "$file" ]]; then
cat "$file"
fi
done
}
#histogram
hist=""
for arg do
if [[ ! -e "$arg" ]]; then
EXT=1;
echo "A FILE DOESNT EXIST" >&2
continue;
elif [[ ! -f "$arg" ]]; then
EXT=1;
echo "A FILE DOESNT EXIST" >&2
continue;
elif [[ ! -r "$arg" ]]; then
EXT=1;
echo "A FILE DOESNT EXIST" >&2
continue;
fi
done
for arg do
hist="$hist""$arg""\n"
done
echo -e "$hist" | cat-all | sortWords
exit $EXT;
Here is what our upload system which does some test to see if our program works says:
Test #6
> b5.sh -r ./easy.txt
ERROR: script output is wrong:
--- expected output
+++ script stdout
## --- line 1 (167 lines) ; +++ no lines ##
-the 89
-steam 46
-a 39
-of 37
-to 35
...
script written 484 lines, while 484 lines are expected
script error output:
A FILE DOESNT EXIST
cat: invalid option -- 'r'
Try `cat --help' for more information.
script exit value: 1
ERROR: Interrupted due to failed test
If anyone could help me I would really appreciate it.
You forgot to move the parameter index position with shift:
"-r")
r=1
shift
;;
shift above moves to the next command line arg: ./easy.txt in your case.
Without it, read -r file will read -r instead of the file name.

Extra character added to the output of an array when looping through it

#!/bin/bash
a=coop; b=(`echo $a | sed 's/\(.\)/\1\n/g'`)
for i in ${b[#]}
do
echo -n $i
count=$((count+1))
if [ $count = 2 ]; then
echo -e '\e[0;34m'$i
shift
echo -ne $*'\e[0m'
fi
done
Output: cooop (the middle one is in blue). What I want the script to do is show the exact word stored in the variable named "a". But as you can see, another "o" is added next to "p". So how can i go about removing the extra letter?
Try this:
#!/bin/bash
blue='\e[0;34m'
nc='\e[0m'
a=coop
b=($(echo $a | sed 's/\(.\)/\1\n/g'))
count=0
for i in ${b[#]}; do
if [ $count = 2 ]; then
echo -ne "${blue}${i}"
echo -ne "${nc}"
else
echo -n "$i"
fi
count=$((count+1))
done

Is there an easy way to determine if user input is an integer in bash?

I am a new student to bash scripting, and I am stumped on an assignment question.
I was wondering if there is an easy way to determine whether a users' input is an integer or not. More specifically, if a user is prompted to input an integer, is there a quick check to validate?
One way is to check whether it contains non-number characters. You replace all digit characters with nothing and check for length -- if there's length there's non-digit characters.
if [[ -n ${input//[0-9]/} ]]; then
echo "Contains letters!"
fi
Another approach is to check whether the variable, evaluated in arithmetic context, is equal to itself. This is bash-specific
if [[ $((foo)) != $foo ]]; then
echo "Not just a number!"
fi
This is kind of a kludge, it's using -eq for something other then what it was intended, but it checks for an integer, if it doesn't find an int it returns both an error which you can toss to /dev/null and a value of false.
read input
if [[ $input ]] && [ $input -eq $input 2>/dev/null ]
then
echo "$input is an integer"
else
echo "$input is not an integer or not defined"
fi
You can test by using Regular expression
if ! [[ "$yournumber" =~ ^[0-9]+$ ]] ;
then exec >&2; echo "error: Not a number"; exit 1
fi
I found this post http://www.unix.com/shell-programming-scripting/21668-how-check-whether-string-number-not.html that talks about this.
If your input does not need to check if there is a +/- on the number, then you can do:
expr $num + 1 2> /dev/null
if [ $? = 0 ]
then
echo "Val was numeric"
else
echo "Val was non-numeric"
fi
Here is another way of doing it. It's probably a bit more elaborate than needed in most cases, but would handle decimals also. I had written the below code to get rounded number. It also checks for numeric input in the process.
#--- getRound -- Gives number rounded to nearest integer -----------------------
# usage: getRound <inputNumber>
#
# echos the rounded number
# Best to use it like:
# roundedNumber=`getRound $Number`
# check the return value ($?) and then process further
#
# Return Value:
# 2 - if <inputNumber> is not passed, or if more arguments are passed
# 3 - if <inputNumber> is not a positive number
# 0 - if <inputNumber> is successfully rounded
#
# Limitation: Cannot be used for negative numbers
#-------------------------------------------------------------------------------
getRound (){
if [ $# -ne 1 ]
then
exit 2
fi
#--- Check if input is a number
Input=$1
AB=`echo A${Input}B | tr -d [:digit:] | tr -d '.'`
if [ "${AB}" != "AB" ] #--- Allow only '.' and digit
then
exit 3
fi
DOTorNone=`echo ${Input} | tr -d [:digit:]` #--- Allow only one '.'
if [ "${DOTorNone}" != "" ] && [ "${DOTorNone}" != "." ]
then
exit 3
fi
echo $Input | awk '{print int($1+0.5)}' #--- Round to nearest integer
}
MyNumber=`getRound $1`
if [ $? -ne 0 ]
then
echo "Empty or invalid input passed"
else
echo "Rounded input: $MyNumber"
fi
This one works for me, handling empty input case.
if [ $input -eq $input 2>/dev/null -o $input -eq 0 2>/dev/null ]
then
echo Integer
else
echo Not an integer
fi

Resources