Getting user input during bash script - bash

I am trying to create my first bash script:
echo "What file are we looking for: "
read FILE
while [ 1 ]
do
ls -lah | grep $FILE
sleep 1;
clear
# Some way to detect user input
if [ user-input ]
then
echo "Input found"
exit 1;
fi
done
Is there a way to look for user input without pausing the program? When I used read input before the if statement, the program stopped until input was...inputted.... The program is supposed to continuously output the file I am look using ls and clear so I can monitor the size as it grows, but when the user inputs any key stroke the program exits.
Like I said this is my first bash script, I do know python pretty well and understand 'coding' but not bash.
Thanks

check Bash input without pausing the script?, seems like a duplicate.
from that link:
read -t0 can be used to probe for input if your process is structured as a loop
#!/bin/bash
a='\|/-'
spin()
{
sleep 0.3
a="${a:1}${a:0:1}"
echo -n $'\e'7$'\r'"${a:1:1}"$'\e'8
}
echo 'try these /|\- , dbpq , |)>)|(<( , =>-<'
echo -n " enter a pattern to spin:"
while true
do
spin
if read -t0
then
read a
echo -n " using $a enter a new pattern:"
fi
done
else you could run one command in the background while promptiong for input in the foreground. etc...

Related

Stop reading from STDOUT if stream is empty in BASH

I am creating a script (myscript.sh) in BASH that reads from STDOUT, typically a stream of data that comes from cat, or from a file and outputs the stream of data (amazing!), like this:
$cat myfile.txt
hello world!
$cat myfile.txt | myscript.sh
hello world!
$myscript.sh myfile.txt
hello world!
But I also would like the following behaviour: if I call the script without arguments I'd like it to output a brief help:
$myscript.sh
I am the help: I just print what you say.
== THE PROBLEM ==
The problem is that I am capturing the stream of data like this:
if [[ $# -eq 0 ]]; then
stream=$(cat <&0)
elif [[ -n "$stream" ]]; then
echo "I am the help: I just print what you say."
else
echo "Unknown error."
fi
And when I call the script with no arguments like this:
$myscript.sh
It SHOULD print the "help" part, but it just keep waiting for a stream of data in line 2 of code above...
Is there any way to tell bash that if nothing comes from STDOUT just break and continue executing?
Thanks in advance.
There's always a standard input stream; if no arguments are given and input isn't redirected, standard input is the terminal.
If you want to treat that specially, use test -t to test if standard input is connected to a terminal.
if [[ $# -eq 0 && -t 0 ]]; then
echo "I am the help: I just print what you say."
else
stream=$(cat -- "$#")
fi
There's no need to test $#. Just pass your arguments to cat; if it gets filenames it will read from them, otherwise it will read from standard input.
I agree to #Barmar's solution.
However, it might be better to entirely avoid a situation where your program behavior depends on whether the input file descriptor is a terminal (there are situations where a terminal is mimicked even though there's none -- in such a situation, your script would just produce the help string).
You could instead introduce a special - argument to explicitly request reading from stdin. This will result in simpler option handling and uniform behavior of your script, no matter what's the environment.
First answer is to help yourself - try running the script with bash -x myscript.sh. It will include lot of information to help you.
If you specific case, the condition $# -eq 0 was flipped. As per requirement, you want to print the help message is NOT ARGUMENT ARE PROVIDED:
if [[ $# -eq 0 ]] ; then
echo "I am the help: I just print what you say."
exit 0
fi
# Rest of you script, read data from file, etc.
cat -- "$#"
Assuming this approach is taken, and if you want to process standard input or a file, simple pass '-' as parameter: cat foobar.txt | myscript.sh -

BASH : control exits CASE logic after entering any condition - Unable to understand the reason

I have an input file, which has below data :
driver2:y
driver5:y
driver3:n
driver1:y
driver4:y
The requirement is, for each driver if the value is "y" then the script has to check for the existence of a set of files that are related to that driver. If all of them exist, then only that driver related next step should be performed.
The main script reads line by line from the input.txt file. and I'm using CASE for this.
My problem is when I try to call the function_exists() from the CASE logic (which are commented in the script as you can see) or If I use any "IF" conditions instead of function_exists() to perform the same check, that particular part in the CASE logic is executing (say driver2, all the code under "driver2)" executes) and the script control is automatically exiting the CASE logic and script is ending, without reading the remaining lines from the input file.
The script is reading all entries, but something is wrong with the CASE logic when I'm using functions and IF conditions.
I'm unable to understand what went wrong with the script, what mistake I'm doing here. Can someone help me with this?
#!/bin/sh
function_exists ()
{
ssh $remote_server "test -e $1"
if [ $? == 0 ]
then
echo "file found : $1"
else
echo "file not found : $1"
fi
echo "***** exiting function"
}
remote_server="vmlinux1"
input="input.txt"
while IFS= read -r line
do
IFS=":"
read -ra arr <<< "$line"
driver=${arr[0]}
export driver=$driver
driverInstall=${arr[1]}
echo "read value" $driver $driverInstall
if [ $driverInstall == 'y' ]; then
case $driver in
driver1)
echo "entered driver 1"
#function_exists /opt/installer/file1.txt
echo "***** control came back to $driver"
;;
driver2)
echo "entered driver 2"
#function_exists /opt/installer/file2.txt
echo "***** control came back to $driver"
continue
;;
driver4)
echo "entered driver 4 - for directory check"
#function_exists /opt/installer/directory1
echo "***** control came back to $driver"
;;
*)
echo "${driver} - NOTHING DEFINED"
;;
esac
else
echo "${driver} - SKIPPED "
fi
done < "$input"
echo "Script ended
========================================"
Connect "ssh" standard input to nirvana otherwise ssh eats remaining lines.
ssh $remote_server "test -e $1" < /dev/null
# you can also use ssh -n
read value driver2 y
entered driver 2
file not found : /opt/installer/file2.txt
***** exiting function
***** control came back to driver2
read value driver5 y
driver5 - NOTHING DEFINED
read value driver3 n
driver3 - SKIPPED
read value driver1 y
entered driver 1
file found : /opt/dev/python/scrapper/main.py
***** exiting function
***** control came back to driver1
read value driver4 y
entered driver 4 - for directory check
file not found : /opt/installer/directory1
***** exiting function
***** control came back to driver4
Script ended
========================================
The problem is that your script runs ssh commands and by default ssh
reads from stdin which is your input file. As a result, you only see
the first line processed, because the command consumes the rest of the
file and your while loop terminates.
This happens not just for ssh, but for any command that reads stdin,
including mplayer, ffmpeg, HandBrakeCLI, and more.
To prevent this, pass the -n option to your ssh command to make it
read from /dev/null instead of stdin. Other commands have similar
flags, or you can universally use < /dev/null

Adding new lines to multiple files

I need to add new lines with specific information to one or multiple files at the same time.
I tried to automate this task using the following script:
for i in /apps/data/FILE*
do
echo "nice weather 20190830 friday" >> $i
done
It does the job yet I wish I can automate it more and let the script ask me for to provide the file name and the line I want to add.
I expect the output to be like
enter file name : file01
enter line to add : IWISHIKNOW HOWTODOTHAT
Thank you everyone.
In order to read user input you can use
read user_input_file
read user_input_text
read user_input_line
You can print before the question as you like with echo -n:
echo -n "enter file name : "
read user_input_file
echo -n "enter line to add : "
read user_input_text
echo -n "enter line position : "
read user_input_line
In order to add line at the desired position you can "play" with head and tail
head -n $[$user_input_line - 1] $user_input_file > $new_file
echo $user_input_text >> $new_file
tail -n +$user_input_line $user_input_file >> $new_file
Requiring interactive input is horrible for automation. Make a command which accepts a message and a list of files to append to as command-line arguments instead.
#!/bin/sh
msg="$1"
shift
echo "$msg" | tee -a "$#"
Usage:
scriptname "today is a nice day" file1 file2 file3
The benefits for interactive use are obvious -- you get to use your shell's history mechanism and filename completion (usually bound to tab) but also it's much easier to build more complicated scripts on top of this one further on.
The design to put the message in the first command-line argument is baffling to newcomers, but allows for a very simple overall design where "the other arguments" (zero or more) are the files you want to manipulate. See how grep has this design, and sed, and many many other standard Unix commands.
You can use read statement to prompt for input,
read does make your script generic, but if you wish to automate it then you have to have an accompanying expect script to provide inputs to the read statement.
Instead you can take in arguments to the script which helps you in automation.. No prompting...
#!/usr/bin/env bash
[[ $# -ne 2 ]] && echo "print usage here" && exit 1
file=$1 && shift
con=$1
for i in `ls $file`
do
echo $con >> $i
done
To use:
./script.sh "<filename>" "<content>"
The quotes are important for the content so that the spaces in the content are considered to be part of it. For filenames use quotes so that the shell does not expand them before calling the script.
Example: ./script.sh "file*" "samdhaskdnf asdfjhasdf"

Bash init script skips reading commands in if loop

I am trying to create an init script for a program in bash. (rhel6)
It checks for the processes first. If processes are found it will echo that program is already online and if not it'll move on to to start the program as a certain user by using launch script. After doing that it should tail the log file of the program and check for a string of words together. If the words are found it should kill tail and echo that program is online.
Here's the start segment.
prog=someProg
user=someUser
threadCount=$(ps -ef | grep $prog |grep -v 'grep' |awk '{ print $2 }'| wc -l)
startb() {
if [ "$threadCount" -eq 2 ]; then
echo "$prog already online."
else
echo "Bringing $prog online."
su $user -c "/path/to/start/script.sh"
tail -f /path/to/$prog/log/file |
while IFS=$'\n' read line
do
if [[ $line == *started\ up\ and\ registered\ in* ]]; then
pkill tail
echo "$prog now online."
fi
done
fi
}
My problems:
The variable $prog doesn't get picked in $threadcount no
matter how I try. (with single and double quotes)
The logic about tailing the log file works randomly. Some times it
just works perfect. It tails and waits till the string is found
before echoing program is online and at times it just starts script
and then echoes that program is online without the tail or wait.
It's unpredictable. I implemented the same logic in stop segment too to monitor log and then echo but even that works the same way as start. Just random.
I am sure that this might look dumb and broken. This is made by picking pieces here and there with my beginner bash skills.
Thanks in advance for suggestions and help.
I can't reproduce the error you are experiencing with the "grep $prog"...sorry.
But for the other part.
I will assume that the script starting your program, the line with su, is starting something in background and that the script end by itself. If not, your example will wait indefinitely.
Could be a personal preference, but when I'm using something like tail to verify lines, I use a named pipe (mkfifo).
That would give something like :
# Getting the tail in background
tail -f /path/to/$prog/log/file > some_fifo &
# Getting the tail PID
tailPID=$!
while read line; do #You don't need to modify/use IFS here.
if [[ $line == *started\ up\ and\ registered\ in* ]]; then
kill -15 $tailPID #since you know the PID you won't kill another tail
echo "$prog now online."
break # don't like the possibility, even remote, of an infinite loop :)
fi
done < some_fifo #reading from the named pipe
Hope it can help you

What is wrong with my bash script?

What I have to to is edit a script given to me that will check if the user has write permission for a file named journal-file in the user's home directory. The script should take appropriate actions if journal-file exists and the user does not have write permission to the file.
Here is what I have written so far:
if [ -w $HOME/journal-file ]
then
file=$HOME/journal-file
date >> file
echo -n "Enter name of person or group: "
read name
echo "$name" >> $file
echo >> $file
cat >> $file
echo "--------------------------------" >> $file
echo >> $file
exit 1
else
echo "You do not have write permission."
exit 1
fi
When I run the script it prompt me to input the name of the person/group, but after I press enter nothing happens. It just sits there allowing me to continue inputting stuff and doesn't continue past that part. Why is it doing this?
The statement:
cat >>$file
will read from standard input and write to the file. That means it will wait until you indicate end of file with something like CTRL-D. It's really no different from just typing cat at a command line and seeing that nothing happens until you enter something and it waits until you indicate end of file.
If you're trying to append another file to the output file, you need to specify its name, such as cat $HOME/myfile.txt >>$file.
If you're trying to get a blank line in there, use echo rather than cat, such as echo >>$file.
You also have a couple of other problems, the first being:
date >> file
since that will try to create a file called file (in your working directory). Use $file instead.
The second is the exit code of 1 in the case where what you're trying to do has succeeded. That may not be a problem now but someone using this at a later date may wonder why it seems to indicate failure always.
To be honest, I'm not really a big fan of the if ... then return else ... construct. I prefer fail-fast with less indentation and better grouping of output redirection, such as:
file=${HOME}/journal-file
if [[ ! -w ${file} ]] ; then
echo "You do not have write permission."
exit 1
fi
echo -n "Enter name of person or group: "
read name
(
date
echo "$name"
echo
echo "--------------------------------"
echo
) >>${file}
I believe that's far more readable and maintainable.
It's this line
cat >> $file
cat is concatenating input from standard input (ie whatever you type) to $file
I think the part
cat >> $file
copies everything from stdin to the file. Maybe if you hid Ctrl+D (end of file) the script can continue.
1) You better check first whether the file exists or not:
[[ -e $HOME/journal-file ]] || \
{ echo "$HOME/journal-file does not exist"; exit 1 }
2) You gotta change "cat >> $file" for whatever you want to do with the file. This is the command that is blocking the execution of the script.

Resources