Converting read variables to lowercase in sh on ubuntu - bash

I have the following situation:
#!/bin/bash
echo "Please enter a word:"
read foobar
The script is getting called with sh script.sh in the Ubuntu terminal.
Searching on the internet for solutions I found:
foobar=${foobar,,}
echo $foobar
The above approach does only work with bash script.sh
So I went on researching and found:
echo $foobar | tr '[:upper:]' '[:lower:]'
Which does indeed work for both, bash and sh, but without the echo it does not work.
It also prints the read input two times instead of one like so:
Y
y
So how can I do this for sh without printing the read input twice?

It's probably because you didn't assign the translated output to a variable yet. Also I suggest quoting your variables around doublequotes to prevent word splitting and pathname expansion.
foobar=$(echo "$foobar" | tr '[:upper:]' '[:lower:]')
If you're using case and you just need to check if an input is y or Y either way you can use a glob pattern like this. There's no need to transliterate it to lowercase form.
case $foobar in
[yY])
echo "User said yes."
;;
*)
echo "User said no."
;;
esac
Also you can somehow suppress showing user input by using -s:
read -s foobar
As a whole to make your code work well in both bash and sh you should already remove the part which is bash specific:
#!/bin/bash
echo "Please enter a word:"
read -s foobar
foobar=$(echo "$foobar" | tr '[:upper:]' '[:lower:]')
echo "$foobar"
And if it's just about showing the smaller form, you can skip the assignment. But don't use another echo along with it:
#!/bin/bash
echo "Please enter a word:"
read -s foobar
echo "$foobar" | tr '[:upper:]' '[:lower:]'
Another alternative form from case. This is meant to be POSIX compatible.
if [ "$foobar" = y ] || [ "$foobar" = Y ]; then
echo "User said yes."
else
echo "User said no."
fi
In bash it could be simply like this. It would work even in earlier versions that doesn't support ${parameter,,} feature.
if [[ $foobar == [yY] ]]; then
echo "User said yes."
else
echo "User said no."
fi

Use -r option to save the input into REPLY variable and then pipe it to tr command.
read -r -p "Enter something: "; echo "${REPLY}" | tr '[:upper:]' '[:lower:]'
Output:
Enter something: HeLLo, WOrlD!
hello, world!
Do you want to save it in a variable? It's simple, just use command substitution:
foobar=$(read -r -p "Enter something: "; echo "${REPLY}" | tr '[:upper:]' '[:lower:]')
Also, here is another style that is a little simpler (maybe):
read -p "Enter something: " text && tr '[:upper:]' '[:lower:]' <<< "${text}"

Related

How can I grep a list of names from case?

So as an example, I have a bunch of apps that are constantly writing to /var/log/app//nonsence.file there's nothing else those folders, just logs from this one set of apps. so I can easily do:
cat /var/log/app/*/nonsence.file
and I'll get a nice stream of the app logs.
Mixed into this stream are periodic references to people. I'd like to build a script to trigger when certain names appear in the stream.
I can do this easily enough:
cat /var/log/app/*/nonsence.file | grep 'greg|john|suzy|stacy'
and I can put THAT into a simple script thusly:
#!/bin/sh
NAME=`cat /var/log/app/*/nonsence.file | grep 'greg\|john\|suzy\|stacy'`
case "$NAME" in
"greg" ) echo "I found greg!" >> ~/names.meh ;;
"john" ) echo "I found john!" >> ~/names.meh ;;
"suzy" ) echo "I found suzy!" >> ~/names.meh ;;
"stacy" ) echo "I found stacy!" >> ~/names.meh ;;
* ) echo "forever alone..." >> ~/names.meh ;;
esac
easy peasy!
the trouble is, the list of names change from time to time and I would really like a neater list.
After some thinking I believe what I REALLY want to do is add each name into the case section only. so what do I need to do in the NAME variable section to tell the command to grep the name referenced in the case section?
cat file | grep is a useless use of cat. Just grep file.
Command in a pipe are by default block buffered.
The >> ~/names.meh is just repetition. Just specify it once for the whole block.
The backticks ` are discouraged. It's preferred to use $(..) instead.
Each time NAME=... is assigned the file is read, while you seem to want to want:
... I'd like to build a script to trigger when certain names appear in the stream.
which suggest you want to react when the name appears in the script, not after some time.
You may try:
patterns=(greg john suzy stacy)
printf "%s\n" /var/log/app/*/nonsence.file |
# tail each file at the same time by spawning for each a background process
xargs -P0 -n1 tail -F -n+1 |
# grep for the patterns
# pass the patterns from a file
# the <(...) is a process substitution, a bash extension
grep --line-buffered -f <(printf "%s\n" "${patterns[#]}") -o |
# for each grepped content execute different action
while IFS= read -r line; do
case "$line" in)
"greg") someaction; ;;
# etc
*) echo "Internal error - unhandled pattern"; ;;
esac
done >> ~/names.me
Because specyfing patterns twice is lame, you could do an associative function to map the patterns to function names, or just use unique function names and geenerate from them the pattern list:
pattern_greg() { echo "greg"; }
pattern_kamil() { echo "well, not greg"; }
patterns=($(declare -F | sed 's/declare -f //; /^pattern_/!d; s/pattern_//'))
... |
while IFS= read -r line; do
if declare -f pattern_"$line" >/dev/null 2>&1; then
pattern_"$line"
else
echo "Internal error occured"
fi
done
alternatively, but I like the functions better:
greg_function() { echo do something; }
kamil_callback() { echo do something else; }
declare -A patterns
patterns=([greg]=greg_function [kamil]=kamil_callback)
... | grep -f <(printf "%s\n" ${!patterns[#]}) ... |
while IFS= read -r line; do
# I think this is how to check if array element is set
if [[ -n "${patterns[$line]}" ]]; then
"${patterns[$line]}"
else
echo error
fi
done

How to cut variables which are beteween quotes from a string

I had problem with cut variables from string in " quotes. I have some scripts to write for my sys classes, I had a problem with a script in which I had to read input from the user in the form of (a="var1", b="var2")
I tried the code below
#!/bin/bash
read input
a=$($input | cut -d '"' -f3)
echo $a
it returns me a error "not found a command" on line 3 I tried to double brackets like
a=$(($input | cut -d '"' -f3)
but it's still wrong.
In a comment the OP gave a working answer (should post it as an answer):
#!/bin/bash
read input
a=$(echo $input | cut -d '"' -f2)
b=$(echo $input | cut -d '"' -f4)
echo sum: $(( a + b))
echo difference: $(( a - b))
This will work for user input that is exactly like a="8", b="5".
Never trust input.
You might want to add the check
if [[ ${input} =~ ^[a-z]+=\"[0-9]+\",\ [a-z]+=\"[0-9]+\"$ ]]; then
echo "Use your code"
else
echo "Incorrect input"
fi
And when you add a check, you might want to execute the input (after replacing the comma with a semicolon).
input='testa="8", testb="5"'
if [[ ${input} =~ ^[a-z]+=\"[0-9]+\",\ [a-z]+=\"[0-9]+\"$ ]];
then
eval $(tr "," ";" <<< ${input})
set | grep -E "^test[ab]="
else
echo no
fi
EDIT:
#PesaThe commented correctly about BASH_REMATCH:
When you use bash and a test on the input you can use
if [[ ${input} =~ ^[a-z]+=\"([0-9]+)\",\ [a-z]+=\"([0-9])+\"$ ]];
then
a="${BASH_REMATCH[1]}"
b="${BASH_REMATCH[2]}"
fi
To extract the digit 1 from a string "var1" you would use a Bash substring replacement most likely:
$ s="var1"
$ echo "${s//[^0-9]/}"
1
Or,
$ a="${s//[^0-9]/}"
$ echo "$a"
1
This works by replacing any non digits in a string with nothing. Which works in your example with a single number field in the string but may not be what you need if you have multiple number fields:
$ s2="1 and a 2 and 3"
$ echo "${s2//[^0-9]/}"
123
In this case, you would use sed or grep awk or a Bash regex to capture the individual number fields and keep them distinct:
$ echo "$s2" | grep -o -E '[[:digit:]]+'
1
2
3

If statement matching words separated by special char

I'm new to unix. I have a file with an unknown amount of lines in format: "password, username" and I'm trying to make a function that checks this file against user inputted login.
What I have so far:
Accounts file format:
AAA###, firstname.lastname
echo "Please enter Username:"
read username
if cut -d "," -f2 accounts | grep -w -q $username
then
echo "Success"
fi
This function will return Success for inputs "firstname" "lastname" and "firstname.lastname" when I only want it to return for "firstname.lastname"
Any help would be appreciated.
You could go for an exact match, with ^ and $ anchors, like this:
echo "Please enter Username:"
read username
if cut -d "," -f2 accounts | grep -q "^$username$"; then
echo "Success"
fi
While this would work even when the user gives an empty input, you might want to explicitly check for that.
If you loop over the file within the shell, you can use string equality operators instead of regular expressions:
read -rp "enter Username (first.last): " username
shopt -s extglob
found=false
while IFS=, read -r pass uname _othertext; do
# from your question, it looks like the separator is "comma space"
# so we'll remove leading whitespace from the $uname
if [[ "$username" = "${uname##+([[:blank:]])}" ]]; then
echo "Success"
found=true
break
fi
done < accounts
if ! $found; then
echo "$username not found in accounts file"
fi
while read loops in the shell are very slow compared to grep, but depending on the size of the accounts file you may not notice.
Based on your comment, the issue is that the field separator is a comma then a space, not just a comma. cut can't do multi-character delimiters, but awk can. In your code, replace
cut -d "," -f2
with
awk -F ", " '{print $2}'
By the way, there are a few things needed to guard against user input:
# Use "-r" to avoid backslash escapes.
read -rp "Please enter Username:" username
# Always quote variables ("$username").
# Use "grep -F" for fixed-string mode.
# Use "--" to prevent arguments being interpreted as options.
if awk -F ", " '{print $2}' accounts | grep -wqF -- "$username"; then
echo "Success"
fi

Two "if" conditions in the same time

I am writing a script to bring me data from other nodes via ssh in a multi selection choice menu, and i want to display a message according to this data.
if [[ "$option" == "1" ]]
then
ssh skyusr#<IP> "export JAVA_HOME=/opt/mesosphere && /var/lib/mesos/slave/slaves/*/frameworks/*/executors/*/runs/latest/apache-cassandra-3.0.10/bin/nodetool -p 7199 status" | sed -n '6,10p' | awk '{print $1,$2}' | grep DN > $file_name
if [ -s $file_name ]
then
echo "All Cassandra Nodes are UP !"
else cat "$file_name"
fi
fi
When i execute the script, i see it does not see the second if condition to display the message .
What is the correct syntax ?
There is, as far as I can see, nothing wrong with the syntax. You might want to do something about spacing etc. to enhance readability, but that is it.
I assumed, that file_name is set somewhere before this part, as is option.
If things do not work as you expect them to work, you can add some statements for debugging purposes, which you must remove later on. For example, in this case, I would like to see the output of the ssh and add some echo's to see the flow-control:
if [[ "$option" == "1" ]] ; then
ssh skyusr#<IP> "export JAVA_HOME=/opt/mesosphere && /var/lib/mesos/slave/slaves/*/frameworks/*/executors/*/runs/latest/apache-cassandra-3.0.10/bin/nodetool -p 7199 status" > tempfile
cat tempfile |
sed -n '6,10p' |
awk '{print $1,$2}' |
grep DN > "$file_name"
if [ -s "$file_name" ] ; then
echo "All Cassandra Nodes are UP !"
else
echo "$file_name is not empty"
cat "$file_name"
fi
fi
You can use the tempfile to verify that your sed, awk, grep combination acts correctly.

Passing empty strings to grep command

I have this script where I ask for 4 patterns and then use those in a grep command. That is, I want to see if a line matches any of the patterns.
echo -n "Enter pattern1"
read pat1
echo -n "Enter pattern2"
read pat2
echo -n "Enter pattern3"
read pat3
echo -n "Enter pattern4"
read pat4
cat somefile.txt | grep $pat1 | grep $pat2 | grep $pat3 | grep $pat4
The problem I'm running into is that if the user doesn't supply one of the patterns (which I want to allow) the grep command doesn't work.
So, is there a way to have grep ignore one of the patterns if it's returned empty?
Your code has lots of problems:
Code duplication
Interactive asking for potentially unused information
using echo -n is not portable
useless use of cat
Here is what I wrote that is closer to what you should use instead:
i=1
printf %s "Enter pattern $i: "
read -r input
while [[ $input ]]; do
pattern+=(-e "$input")
let i++
printf %s "Enter pattern $i (Enter or Ctrl+D to stop entering patterns): "
read -r input
done
echo
grep "${pattern[#]}" somefile.txt
EDIT: This does not answer OP's question, this searches for multiple patterns with OR instead of AND...
Here is a working AND solution (it will stop prompting for patterns on the first empty one or after the 4th one):
pattern=
for i in {1..4}; do
printf %s "Enter pattern $i: "
read -r input
[[ $input ]] || break
pattern="${pattern:+"$pattern && "}/${input//\//\\/}/"
done
echo # skip a line
awk "$pattern" somefile.txt
Here are some links from which you can learn how to program in bash:
Bash Guide
Bash FAQ

Resources