How can I read user input in a Bash script? [duplicate] - bash

This question already has answers here:
How do I read user input into a variable in Bash?
(6 answers)
Closed 4 years ago.
Using Bash, I tried to read input from the user like this:
#!/bin/bash
function read_from_user {
cat | echo
}
echo 'Do you want to create the folder "new.folder" ?'
var=`read_from_user`
if [[ ${var} == yes ]]; then
mkdir new.folder
fi
echo 'var is: ${var}'
But it's not working, var is empty, even though the user input is not empty.
How can I read user input from my Bash script?

You should use read:
#!/bin/bash
echo 'Do you want to create the folder "new.folder" ?'
read var
if [[ "$var" == "yes" ]]; then
mkdir new.folder
fi
echo "var is: $var"
If you really want to use cat, you could do this, as cat without any argument reads from stdin:
#!/bin/bash
echo 'Do you want to create the folder "new.folder" ?'
var=$(cat)
if [[ "$var" == "yes" ]]; then
mkdir new.folder
fi
echo "var is: $var"
However, you would have to use CTRL + D to send on EOF to your program after typing your input. Otherwise cat will wait for more. read is a cleaner way to ask a user for input.

Your code is almost correct, you just need to change your function to read user input into a variable call var. Also you need to change your code in two place. One in the function and one at the place where you are calling your function. I have modified your code like below:-
#!/bin/bash
function read_from_user {
read -r var #here you are reading user input to variable `var`
}
echo 'Do you want to create the folder "new.folder" ?'
#var=`read_from_user`
read_from_user #here you are calling the function to read user input
if [[ ${var} == yes ]]; then
mkdir new.folder
fi
echo "var is: ${var}"
Also always compare two string like if [[ "${var}" == "yes" ]]; but still your above if condition will also work perfectly.
Also best way to do it like below where you don't need a separate echo statement and input will be read at the end out output message:-
#!/bin/bash
function read_from_user {
read -p 'Do you want to create the folder "new.folder" ? ' var
}
#echo 'Do you want to create the folder "new.folder" ?'
#var=`read_from_user`
read_from_user
if [[ "${var}" == "yes" ]]; then
mkdir new.folder
fi
echo "var is: ${var}"

Related

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

Add lines to a document if they do not already exist within the document

I am trying to say, if document does not exist, then create document. Next read each line of the document and if none of the lines match the $site/$name variables, then add the $site/$name variable into the document.
#!/bin/bash
site=http://example.com
doc=$HOME/myfile.txt
if [ ! -f $doc ]
then
touch $doc
fi
read -p "name? " name
while read lines
do
if [[ $lines != $site/$name ]]
then
echo $site/$name >> $doc
fi
done <$doc
echo $doc
echo $site
echo $name
echo $site/$name
echo $lines
Typing test at the read -p prompt the results are
path/to/myfile.txt
http://example.com
test
http://example.com/test
I feel like I should know this but I'm just not seeing it. What am I doing wrong?
If the file is initially empty, you'll never enter the loop, and thus never add the line. If the file is not empty, you'd add your line once for every non-matching line anyway. Try this: set a flag to indicate whether or not to add the line, then read through the file. If you ever find a matching line, clear the flag to prevent the line from being added after the loop.
do_it=true
while read lines
do
if [[ $lines = $site/$name ]]
then
do_it=false
break
fi
done < "$doc"
if [[ $do_it = true ]]; then
echo "$site/$name" >> "$doc"
fi
The following creates the file if it doesn't exist. It then checks to see if it contains $site/$name. If it doesn't find it, it adds the string to the end of the file:
#!/bin/bash
site=http://example.com
doc=$HOME/myfile.txt
read -p "name? " name
touch "$doc"
grep -q "$site/$name" "$doc" || echo "$site/$name" >>"$doc"
How it works
touch "$doc"
This creates the file if it doesn't exist. If it does already exist, the only side-effect of running this command is that the file's timestamp is updated.
grep -q "$site/$name" || echo "$site/$name" >>"$doc"
The grep command sets its exit code to true if it finds the string. If it doesn't find it, then the "or" clause (in shell, || means logical-or) is triggered and the echo command adds the string to the end of the file.

How to loop script till user input is empty?

I am trying to make my script to repeat till the user leaves the block question empty. I just got the loop to run, but I can not find a way to make it possible to stop it when block is empty.
I hope some one can help me!!
#!/bin/tcsh -f
#
set word="start"
until ($word !=""); do
#First ask for Compound and Block Name.
echo -n "please enter block name: "
read block
echo -n "please enter compound name: "
read compound
#Now coping template with new name
#
cp Template $block
#
for line in `cat $block`;do
echo $line | sed -e "s/test1/${block}/g" -e "s/test2/${compound}/g" >>./tmp124.txt
done
mv ./tmp124.txt $block
done
Do you want to use bash or csh? You are using bash syntax but tagged your question csh and call tcsh in the first line of your code.
To answer your question, here are examples of how to iterate on standard input until some input is empty:
For tcsh:
#!/bin/tcsh
while ( 1 )
set word = "$<"
if ( "$word" == "" ) then
break
endif
# rest of code...
end
For bash:
#!/bin/bash
while read word; do
if [ -z $word ]; then
break
fi
# rest of code...
done
Use "Until do" loop,
Eg :
For session variable i am assigning default value, Then entering loop. User can pass any value on each prompt when the value is empty, Loop will Terminate and exit the script.
session="Mysession"
until [$session -eq $null]
do
echo $session
echo "Leave Blank to Terminate session"
read -p "Enter session name : " session
done
echo "Exiting.."

How to use a text file for multiple variable in bash

I want to make an bash script for things I use much and for easy access of things but I want to make an firstrun setup that saves the typed paths to programs or commands in a txt file. But how can I do that. And how can I include the lines of the text file to multiple variables?
After a lot of testing I could use the 2 anwsers given. I need to store a variable directly to a textfile and not asking a user for his details and then stores that to a file
So I want it to be like this
if [[ -d "/home/$(whoami)/.minecraft" && ! -L "/home/$(whoami)/.minecraft" ]] ; then
echo "Minecraft found"
minecraft="/home/$(whoami)/Desktop/shortcuts/Minecraft.jar" > safetofile
# This ^ needs to be stored on a line in the textfile
else
echo "No Minecraft found"
fi
if [[ -d "/home/$(whoami)/.technic" && ! -L "/home/$(whoami)/.technic" ]]; then
echo "Technic found"
technic="/home/$(whoami)/Desktop/shortcuts/TechnicLauncher.jar" > safetofile
# This ^ also needs to be stored on an other line in the textfile
else
echo "No Technic found"
fi
I really want to have an anwser to this because I want to script bash. I already experience in bash scripting.
Here's an example:
#!/bin/bash
if [[ -f ~/.myname ]]
then
name=$(< ~/.myname)
else
echo "First time setup. Please enter your name:"
read name
echo "$name" > ~/.myname
fi
echo "Hello $name!"
The first time this script is run, it will ask the user for their name and save it. The next time, it will load the name from the file instead of asking.
#!/bin/bash
# file to save the vars
init_file=~/.init_vars.txt
# save_to_file - subroutine to read var and save to file
# first arg is the var, assumes init_file already exists
save_to_file()
{
echo "Enter $1:"
read val
# check if val has any spaces in them, you will need to quote them if so
case "$val" in
*\ *)
# quote with double quotes before saving to init_file
echo "$1=\"$val\"" >> $init_file
;;
*)
# save var=val to file
echo "$1=$val" >> $init_file
;;
esac
}
if [[ ! -f $init_file ]]
then
# init_file doesnt exist, this will come here only once
# create an empty init_file
touch $init_file
# vars to be read and saved in file, modify accordingly
for var in "name" "age" "country"
do
# call subroutine
save_to_file "$var"
done
fi
# init_file now has three entries,
# name=val1
# age=val2
# country=val3
# source the init_file which will read and execute commands from init_file,
# which set the three variables
. ${init_file}
# echo to make sure it is working
echo $name $age $country

Resources