exit from STDIN from bash script when the user want to close it - linux

I'm automating the file creation from a bash script. I generated a file rc_notes.txt which has commit messages from two tags and want to re-write that in a new file as rc_device.txt.
I want the user to write the customer release notes and exit from the BASH STDIN that I prompt in the terminal.
The problem in my script is I'm not able to trap the close of file.
Wondering how to do. I don't want to trap the close signal. I want to enter magic string example: Done or some string that triggers the closure of STDIN, that exit from the script gracefully.
My script:
#/bin/bash
set -e
echo "Creating the release candiate text"
rc_file=rc_updater_notes.txt
echo "=========Reading the released commit message file=========="
cat $rc_file
echo "=========End of the commit message file=========="
echo "Now write the release notes"
#exec < /dev/tty
while read line
do
echo "$line"
done < "${1:-/dev/stdin}" > rc_file.txt
It does create the file but I need to exit manually by entering ctrl+D or ctrl+z. I don't want to do that. Any suggestions?

To break the loop when "Done" is entered
while read line
do
if [[ $line = Done ]]; then
break;
fi
echo "$line"
done < "${1:-/dev/stdin}" > rc_file.txt
or
while read line && [[ $line != Done ]]
do
echo "$line"
done < "${1:-/dev/stdin}" > rc_file.txt

Related

How to avoid the command execution when appending lines to a file

I'm trying to save the content of script into a file using command line, but I noticed that when the tee command detects linux commands such as $(/usr/bin/id -u), it execute the commands rather than saving the lines as it is. How to avoid the execution of the commands and saving the text exactly as I entered it?
$tee -a test.sh << EOF
if [[ $(/usr/bin/id -u) -ne 0 ]]; then
echo You are not running as the root user.
exit 1;
fi;
EOF
if [[ 502 -ne 0 ]]; then
echo You are not running as the root user.
exit 1;
fi;
Complete script contains many more lines, but I chose /usr/bin/id -u as a sample.
This has nothing to do with tee or appending to the file, it's how here-documents work. Normally variable expansion and command substitution is done in them.
Put single quotes around the EOF marker. This will treat the here-document like a single-quoted string, so that $ will not expand variables or execute command substitutions.
tee -a test.sh << 'EOF'
if [[ $(/usr/bin/id -u) -ne 0 ]]; then
echo You are not running as the root user.
exit 1;
fi;
EOF
if [[ $(/usr/bin/id -u) -ne 0 ]]; then
echo You are not running as the root user.
exit 1;
fi;

Why can't change the input data if the variable declaration is inside of a for loop in bash

So im trying to make an infinity loop, that creates libraries.
but the file variable take input only once.
code:
for (( ; ; ))
do
file=${1?Error: no input}
mkdir "$file"
sleep 1
done
There's nothing in that loop that asks for input. $1 is provided once by the user as they run the script (before the loop even starts). The standard way to request input in a shell script is with the read command. Something like this:
while read -p "Enter a directory to create: " file; do
mkdir "$file"
done
This loop will terminate when it receives an end-of-file, which means the user must press Control-D to exit it. If you want to exit if the user just presses return without entering anything, you could do this:
while read -p "Enter a directory to create: " file; do
if [ -z "$file" ]; then
echo "Error: no input" >&2
break # This exits the while loop
fi
mkdir "$file"
done

Run commands from a text file through a bash script

I am attempting to write a script that will read through a text file, and then execute every line that begins with the word "run" or "chk" as a command. This is what I have thus far:
#!/bin/bash
counter=1
for i in $#
do
while read -r line
do
if [[ ${line:0:4} == "run " ]]
then
echo "Now running line $counter"
${line:4:${#line}}
elif [[ ${line:0:4} == "chk " ]]
then
echo "Now checking line $counter"
${line:4:${#line}}
elif [[ ${line:0:2} == "# " ]]
then
echo "Line $counter is a comment"
else
echo "Line $counter: '$line' is an invalid line"
fi
counter=$((counter+1))
done<$i
done
However, when I feed it a text file with, for example the commands
run echo > temp.txt
It does not actually create a file called temp.txt, it just echoes "> temp.txt" back to the stdout. It also does a similar thing when I attempt to do something like
run program arguments > filename.txt
It does not put the output of the program in a file as I want, but it rather tries to treat the '>' as a file name.
I know this is a super specific and probably obvious thing, but I am very new to bash and all shell scripting.
Thanks
You need to use eval to do all the normal shell parsing of the variable:
eval "${line:4}"
You also don't need :${#line}. If you leave out the length, it defaults to the rest of the string.

Filtering shell script output within itself, the script is not terminated

I want a Bash script to generate some output messages. The script is supposed to capture messages, do some filtering, transform, and then output them to the screen.
The filtered results are correct in the output, but the script is not terminated. I must press a return key to finish it. How do I fix it?
Demo script:
#!/bin/bash
exec &> >(
{
while read line; do
[ "$line" = "exit" ] && break
echo "`date +%H:%M:%S.%N` $line"
done
echo "while finish"
} )
for ((i=3;i--;)); do
echo "text $i"
done
echo "exit"
The script does terminate, but the script itself finishes before the background process that writes the output does, so the prompt is displayed first, then the output, leaving your terminal with a blank line that looks like the script is still running. You could type any command instead of hitting return, and that command would execute.
To avoid this, you need to run the while loop in an explicit background job that you can wait on before exiting your script.
mkfifo logpipe
trap 'wait $logger_pid; rm logpipe' EXIT
while read line; do
[ "$line" = "exit" ] && break
echo "$(date +%H:%M:%S.%N) $line"
done < logpipe &
logger_pid=$!
exec &> logpipe
# ==========
for ((i=3;i--;)); do
echo "text $i"
done
echo "exit"
The while loop runs in the background, reading its input from the named pipe logpipe. Once that is running, you can redirect all your output to the pipe and start your "main" script. The exit trap ensures that your script doesn't actually exit until the while loop completes; it also cleans up the named pipe for you.
You might not have noticed yet, but there is no guarantee that the while loop will receive the merged standard output and standard error in the exact order in which things are written to them. For instance,
echo out1
echo err1 >&2
echo out2
echo err2 >&2
may end up being read as
out1
err1
err2
out2
Each stream itself will remain in order, but the two could be arbitrarily merged.

How to portable read a text file line by line in bash

For processing a text file in bash line by line, I usually implement a while loop like this:
function doSomething() {
local inputFile="$1"
local fd=""
local line=""
exec {fd}<"$inputFile" # open file
echo "Opened ${inputFile} for read using descriptor ${fd}"
while IFS='' read -r -u $fd line || [[ -n "$line" ]]; do
echo "read = \"$line\""
done
exec {fd}<&- # close file
return 0
}
This works on my Linux but unfortunately not in OSX. For OSX I currently have to change the code to something like this:
exec 3<"$inputFile" # open file
while IFS='' read -r -u 3 line || [[ -n "$line" ]]; do
echo "read = \"$line\""
done
exec 3<&- # close file
But this has the disadvantage, that I have to manage the file descriptor numbers by myself (in the first script, I let bash choose an available file descriptor number).
Did someone have a solution for this which works for both Linux and OSX?
Note that for some reason, I don't want to use piping or I/O redirection to the complete loop like this (because I don't want to execute the loop in a different process):
while IFS='' read -r line || [[ -n "$line" ]]; do
echo "read = \"$line\""
done < "$inputFile"
The last loop will not fork a new process. You can verify that by printing "$BASHPID" in and outside of the loop.
New processes are only created for pipelines. Simple redirections are handled by temporary dups within the bash process.
Feel free to use standard stdin/stdout redirection. It's no more expensive than redirection done with the exec builtin.

Resources