How to use Read command in echo like fill in the blanks - linux

Am writing a script.Eg:
echo "my name is 'read' and am from 'read' city" > outfile.txt
When it runs it's not printing the sentence first, i.e my name is. Rather it's asking first to enter 2 inputs for 2 read commands used, then its forming the complete sentence like "my name is sudhir and am from vizag city"
I want script to execute 1st "my name is read(ask for input) and am from read(ask for another input) city" and after giving inputs it should redirect to outfile.txt in one shot.
How to handle this? Is it feasible to achieve in single sentence?
Because I want to use same logic for 480 questions all in one file and persons how don't have any scripting knowledge should able to add more questions taking reference of previous questions present in same file.

There isn't a pretty way to do this, since read writes the newline character from the input to the terminal, but we can do this in two passes.
Get the input from the user
Write the results to the file
You could put the following in a script
#!/bin/bash
echo -n 'my name is '; read -r name
echo -n ' and I am from '; read -r city
echo ' city'
printf "my name is %s and I am from %s city\n" \
"$name" "$city" > output.txt
And to the user it would look like this
my name is sudhir
and I am from vizag
city
But it would be like this in the file
my name is sudhir and I am from vizag city

You can write a function that takes a string with placeholders and asks the user to input for each of them:
#!/bin/bash
fill() {
arg="$*"
result=""
while [[ "$arg" =~ ([^_]*)(_+)(.*) ]]
do
read -rp "${BASH_REMATCH[1]# }${BASH_REMATCH[2]}: " input
result+="${BASH_REMATCH[1]}${input}"
arg="${BASH_REMATCH[3]}"
done
result+="$arg"
printf '%s\n' "$result"
}
exec > outputfile
fill "My name is ____ and I am from ___."
fill "My new years resolution is ____."
Example:
$ ./myscript
My name is ____: Sudhir
and I am from ___: Vizag
My new years resolution is ____: learning Bash instead of asking SO to write my scripts
$ cat outputfile
My name is Sudhir and I am from Vizag.
My new years resolution is learning Bash instead of asking SO to write my scripts.

Related

Inserting the date into a "MAIL" message body in a BASH script

I have a relatively simple BASH script to send mail from my Raspberry Pi. The first argument is the Subject line and the second is a string of data files to be attached.
It is basically working when I specify the message body as a file (line 6). But if I try to create a text sting containing the date as the message body it fails (line7). Here is my script:
#!/bin/bash
#echo $2
# To
TO="me#hotmail.com"
# Message
MESSAGE="output/MessageBody.txt"
MESSAGEx="Midnight `date '+%Y-%m-%d %H:%M:%S %Z'` Pi report"
echo $MESSAGE
echo $MESSAGEx
temp=$(echo $2 | tr ";" "\n")
declare -a attargs
for att in $temp; do
attargs+=( "-A" "$att" )
done
# Sending email using /bin/mail
/usr/bin/mail -s "$1" "$TO" ${attargs[#]} < $MESSAGEx
Here is the output from this command
/usr/pgms/sendtome.sh "test message" "/mnt/usbdrive/output/JSONstart.txt;/mnt/usbdrive/output/Outback_error.log;/mnt/usbdrive/output/OutbackReaderPrint.txt"
when I specify MESSAGEx as the message body:
/mnt/usbdrive/output/MessageBody.txt
Midnight 2019-08-14 07:40:31 MDT Pi report
/usr/pgms/sendtome.sh: line 22: $MESSAGEx: ambiguous redirect
If I use MESSAGE, ie the text file reference, it works.
How can it create a message body text paragraph which contains the date or some other item? Thanks....RDK
There's a number of issues here.
You should generally quote strings. Without quoting, the string after < is split (hence the error message) and the array you took so much care to collect will lose its purpose.
The thing after < needs to be the name of a file. In Bash you can use a here string <<<"$MESSAGEx" but the common and simple portable solution is to echo (or better printf) its value into a pipe.
You should prefer lower case for your private variable names, but this is mainly a stylistic recommendation. (There are reserved variables like PATH and SHELL which you really don't want to clobber; POSIX reserves upper case variable names for system use.)
Here's a refactoring which attempts to address these concerns.
#!/bin/bash
to="me#hotmail.com"
# Message
#msgfile="output/MessageBody.txt"
msgbody="Midnight `date '+%Y-%m-%d %H:%M:%S %Z'` Pi report"
#echo "$msgfile"
#echo "$msgbody"
declare -a attargs
for att in $(echo "$2" | tr ";" "\n"); do
attargs+=( "-A" "$att" )
done
/usr/bin/mail -s "$1" "${attargs[#]}" "$to"<<< "$msgbody"
Perhaps a better design would be to just shift the first argument and then use "$#" as the list of files to attach.

Expanding a string with a variable reference later, after the variable is assigned

I'm trying to combine two lists containing names (if available) and emails with a standard email text in bash (shell)
(I had to delete the irrelevant code as it contains some private info, so some of the code might look unusal.)
The first half of the code checks if there is a name list along with the email list.
The second half combines only the email address and text if no name is available, if the name list is available it also 'tries' to combine the name, email and text.
f1 = email list and f2 = name list.
As you can see in the first half of the code below, $f2 should show the names if the list is available but it does not show anything in the log file.
I been trying to sort this problem out for two days but nothing has worked. When names are available it always outputs as "Hello ..." when it should be "Hello John D..."
#FIRST HALF
if [ "$names" = "no" ]
then
text="Hello..."
elif [ "$names" = "yes" ]
then
text="Hello $f2..."
fi
#SECOND HALF
if [ "$names" = "no" ]
then
for i in $(cat $emaillist); do
echo "$text" >> /root/log
echo "$i" >> /root/log
done
elif [ "$names" = "yes" ]
then
paste $emaillist $namelist | while IFS="$(printf '\t')" read -r f1 f2
do
echo "$text" >> /root/log
echo "$f1" >> /root/log
done
fi
When you run text="Hello $f2", $f2 is looked up at the time of the assignment; an exact string is assigned to text, and only that exact string is used later, on echo "$text".
This is very desirable behavior: If shell variables' values could run arbitrary code, it would be impossible to write shell scripts that handled untrusted data safely... but it does mean that implementing your program requires some changes.
If you want to defer evaluation (looking up the value of $f2 at expansion time rather than assignment), don't use a shell variable at all: Use a function instead.
case $names in
yes) write_greeting() { echo "Hello $name..."; };;
*) write_greeting() { echo "Hello..."; };;
esac
while read -r name <&3 && read -r email <&4; do
write_greeting
echo "$email"
done 3<"$namelist" 4<"$emaillist" >>/root/log
Some enhancements in the code above:
You don't need paste to read from two streams in lockstep; you can simply open them on different file descriptors (above, FDs 3 and 4 are chosen; only 0, 1 and 2 are reserved, so larger numbers could have been selected as well) with a separate read command for each.
Opening your output sink only once for the entire loop (by putting the redirection after the done) is far more efficient than re-opening it every time you want to write a single line.
Expansions, such as "$namelist" and "$emaillist", are always quoted; this makes code more reliable if dealing with filenames with unusual characters (including spaces and glob expressions), or if IFS is at a non-default value.

Confused with shell script reading CSV data from file

I'm fairly new to Linux/CentOS and shell scripting. Let's say I have a file named useraccounts.list. This file contains CSV data in this specific order:
first name, middle initial, last name, username, password
for example:
John,N,Snow,seords,cuai2Ohzdh
What I'm trying to do is traverse through this list creating user accounts from the provided data, and with the password at the end of each line. I would like to store it using the passwd command for each individual user.
Here is what I have so far:
#!/bin/sh
for i in 'more useraccounts.list'
do
echo "$1 $2 $3 $4 $5 $6"
done <file.txt
Can someone please help me out?
Before you get to how you will store the password, you need to sort out how you will read the values from the file correctly. In bash, the primary tool for determining how the shell will break a line into individual tokens is the Internal Field Separator (default: space tab newline). The process is called word-splitting The IFS variable allows you to set the characters that will control word-splitting.
You can use that to your advantage in reading lines such as yours by including a comma in IFS. This will allow you to specify individual variable to read each name, initial, last, user name and password into. A while loop is the normal way to accomplish this -- and it allows you to set the IFS for that block of code without effecting the remainder of the script.
An Example, in your case, would be:
#!/bin/bash
[ -z "$1" ] && { ## validate one argument given on command line
printf "error: insufficient input. usage: %s filename.\n" "${0##*/}"
exit 1
}
[ -r "$1" ] || { ## validate it is a readable filename
printf "error: file not found/readable '%s'.\n" "$1"
exit 1
}
## read each line in file separated by ','
# set Internal Field Separator to break on ',' and '\n'
# protect against lack of '\n' on last line with $pw test
while IFS=$',\n' read -r first mi last uname pw || [ -n "$pw" ]; do
printf "name: %-5s %s. %-6s user: %s pass: %s\n" \
"$first" "$mi" "$last" "$uname" "$pw"
## Create User Accounts/Store Password Here...
done <"$1"
exit 0
Input
$ cat dat/useracct.txt
John,N,Snow,seords,cuai2Ohzdh
Jill,O,Rain,reords,cuai3Ohzdh
Jane,P,Sleet,peords,cuai4Ohzdh
Output
$ bash readuserfile.sh dat/useracct.txt
name: John N. Snow user: seords pass: cuai2Ohzdh
name: Jill O. Rain user: reords pass: cuai3Ohzdh
name: Jane P. Sleet user: peords pass: cuai4Ohzdh
You can then create the user accounts with the desired options and store the password any way you like. Let me know if you have any questions.

Bash script looping after end of file

My bash script asks user for their first name, last name, address and phone number and writes this information that is input by the user to a file of the format "firstname.lastname"; however I want to repeat this a number of times (I actually wanted to do something like a do..while loop where it runs atleast one time and asks user if they want to continue creating accounts or not but I see there is no do...while for bash it seems). So when I execute this bash script in the terminal it will ask for how many accounts to be made and I provide all the input but the it only runs one time. What am I doing wrong? Here is my script.
#!bin/bash
echo "How many accounts are you creating?"
read num
echo "Enter first name"
read fName
echo "Enter last name"
read lName
echo "Enter address"
read add
echo "Enter phone number"
read phn
echo "Enter gender m for male and f for female"
read gender
if [ "$gender" == "m" ]
then
sex="male"
elif [ "$gender" == "f" ]
then
sex="female"
else
echo"Invalid gender. Restart the script and enter a valid gender"
exit 0
fi
for (( i = 0; i<=num; i++))
do
cat > $fName.$lName <<-EOF
Full Name: $fName $lName
Address: $add
Phone number: $phn
Gender: $gender
EOF
done
zerobandwidth's answer is correct, but as an alternative answer, it is in fact quite easy to do what you initially wanted to do:
while true; do
# Your account creation code goes here
echo "Create another account (y/n)?"
read another
if [ "$another" != y ]; then
break
fi
done
Put your input code inside the loop, or wrap that code in a function and call the function inside the loop.

Check user input against words in text file

I am working on an option for my program and it should work like this:
user inputs title
user inputs author
system then checks user's title & author input in the text file named BookDB.txt
if there is already existing record in the text file, system will prompt an error
else it will continue user to input price, quantity available and quantity sold.
book will then be added
I tried playing around with grep but to no avail.
Below are my codes for this particular function.
function fnAddBook()
{
echo "Title: "
read inputTitle
echo "Author: "
read inputAuthor
if grep -Fq "$inputTitle" BookDB.txt; then
if grep -Fq "$inputAuthor" BookDB.txt; then
echo "Error!"
fi
else
echo "Price: "
read inputPrice
echo "$inputTitle:$inputAuthor:$inputPrice" >> BookDB.txt
echo "New Book successfully added!"
fi
}
contents of BookDB.txt
format of the contents | Title:Author:Price:QtyAvail:QtySold
Hello World:Andre:10.50:10:5
Three Little Pig:Andrew Lim:89.10:290:189
All About Ubuntu:Ubuntu Team:76.00:55:133
Catch Me If You Can:Mary Ann:23.60:6:2
Happy Day:Mary Ann:12.99:197:101
UPDATED PROBLEM:
In this case, even if I typed "Catch Me If You Can" as title + "Ubuntu Team" as Author, it raises the error. How can I modify the codes such that it checks line by line?
Thanks in advance to those who helped! :)
There are 3 problems with your code.
The first is that the x option to grep causes it to match only complete lines, and since you put author and title on the same line, this will not match.
With the x option "Gaiman" does not match "Gaiman:Nation:$20", if you remove the x from the grep-options, this will work.
The second problem is that the two greps are independent of eachother. Thus if you have a book titled 'Nation' and a book by 'Gaiman' it will be considered a match, even if the 'Nation' book you have is 'The wealth of Nations' and the Gaiman book you've got is 'Anansi Boys'.
The third problem is that grep will find partial matches. If you try to enter the book "It", then grep will conclude it's already in the database, because "It came from the desert" is.
You need a sentinel-value to delineate the titles to fix this. (the sentinel must be some character that cannot exist in book-titles or author-names)
function fnAddBook()
{
echo "Title: "
read inputTitle
echo "Author: "
read inputAuthor
if grep -Fq "$inputTitle:inputAuthor:" BookDB.txt
then
echo "Error!"
else
echo "Price: "
read inputPrice
echo "$inputTitle:$inputAuthor:$inputPrice" >> BookDB.txt
echo "New Book successfully added!"
fi
}
This assumes that ':' cannot occur in authornames or booknames.

Resources