How to read from file *and* stdin in bash - bash

Here is my task: read some data from file line by line. For each line, if it satisfies some condition, then ask user to input something and proceed based on the user's input.
I know how to read content line-by-line from a shell script:
while read line; do
echo $line
done < file.txt
However, what if I want to interact with the user inside the loop body. Conceptually, here is what I want:
while read line; do
echo "Is this what you want: $line [Y]es/[n]o"
# Here is the problem:
# I want to read something from standard input here.
# However, inside the loop body, the standard input is redirected to file.txt
read INPUT
if [[ $INPUT == "Y" ]]; then
echo $line
fi
done < file.txt
Should I use another way to read file? or another way to read stdin?

You can open the file on a file descriptor other than standard input. For example:
while read -u 3 line; do # read from fd 3
read -p "Y or N: " INPUT # read from standard input
if [[ $INPUT == "Y" ]]; then
echo $line
fi
done 3< file.txt # open file on fd 3 for input

Related

Read each line of file with a condition to store line to another file in bash

I am a beginner and I am wondering if I can get help with this, please. The code should read one line from a file, display it, meet a condition, and then move on to the next line and repeat for as many lines as are in the file. I have been able to come up with a way to do this but this would be better if it was a loop instead. This is what I have so far
output=$(cat urls.txt | sed -n '1p')
read -p "Store $output y or n ?" deci
if [ $deci == "y" ];
then
sed -n '1p' urls.txt >> saved_domains.txt
fi
output=$(cat urls.txt | sed -n '2p')
read -p "Store $output y or n ?" deci
if [ $deci == "y" ];
then
sed -n '2p' urls.txt >> saved_domains.txt
fi
And this goes on, line by line
#!/bin/bash
while read output
do
read -p "Store $output y or n ?" deci < /dev/tty
if [[ "$deci" == "y" ]]
then
echo "$output" >> saved_domains.txt
fi
done < url.txt
You can read the url.txt file into an "output" variable through a while loop and use this to read the output from the user and append to the saved_domains.txt file accordingly.
One thing to note is the use of the < /dev/tty at the end of the read command for user input. This is not usually needed but as we are already reading from standard input through reading from the file, we need to specifically specify that we are reading from the terminal (/dev/tty)

Wait for keypress in a while loop and stop script

I'd like to wait for keypress and exit when pressed the letter q.
The script isn't waiting for a key. How to correct it?
while read line
do
...
while :
do
read -n 1 key
if [[ $key = q ]]
then
break
fi
done
done < $1
read reads the input.
In your script, the input is changed to $1.
The first level while loop is reading a line from the file for which the name is stored into $1, and read -n 1 key reads and stores the first char of the next line from the same file.
Give a try to that :
while read line ; do
while : ; do
read -n 1 key <&1
if [[ $key = q ]] ; then
break
fi
done
done < $1
<&1 is the standard input.
The script isn't waiting for a key.
Because the command read is getting its input from the redirected file in:
done < $1 ### Should be "$1".
That file is consumed by both read commands (and anything else inside the loop that read stdin).
The correct solution for shell's read that have the option -u (and bash does), is to define the fd (file descriptor) to use in each read while the file is redirected to some fd number (greater than 2):
while read -u 3 line ; do
while : ; do
read -u 1 -n 1 key
if [[ $key = q ]] ; then
break
fi
done
echo "$line"
done 3< "$1"
That makes the first read get the input from fd 3 which comes from the file (done 3< "$1"), and the second read get the input from fd 1 (stdin).
For POSIX shells, read does not have the -u option, we need to perform some redirections to get the same general effect:
#!/bin/dash
while read line <&3; do
while : ; do
read key <&1
if [ "$key" = q ] ; then
break
fi
done
done 3< "$1"
Sadly, this also remove the -n 1 option from read and each key from the keyboard must be followed by pressing Enter.
To actually read one character we may use dd. And we also may set the actual terminal as /dev/tty (blocks any other redirection) and if we need to hide the text typed (or passwords) use stty -echo:
#!/bin/dash
while read line <&3; do
while : ; do
stty raw -echo
key=$(dd bs=1 count=1 </dev/tty 2> /dev/null)
stty -raw echo
if [ "$key" = q ] ; then
break
fi
done
echo "$line"
done 3< "$1"
Caveat: setting stty raw will prevent the effect of keys like CTRL-C (be careful).

Parsing .csv file in bash, not reading final line

I'm trying to parse a csv file I made with Google Spreadsheet. It's very simple for testing purposes, and is basically:
1,2
3,4
5,6
The problem is that the csv doesn't end in a newline character so when I cat the file in BASH, I get
MacBook-Pro:Desktop kkSlider$ cat test.csv
1,2
3,4
5,6MacBook-Pro:Desktop kkSlider$
I just want to read line by line in a BASH script using a while loop that every guide suggests, and my script looks like this:
while IFS=',' read -r last first
do
echo "$last $first"
done < test.csv
The output is:
MacBook-Pro:Desktop kkSlider$ ./test.sh
1 2
3 4
Any ideas on how I could have it read that last line and echo it?
Thanks in advance.
You can force the input to your loop to end with a newline thus:
#!/bin/bash
(cat test.csv ; echo) | while IFS=',' read -r last first
do
echo "$last $first"
done
Unfortunately, this may result in an empty line at the end of your output if the input already has a newline at the end. You can fix that with a little addition:
!/bin/bash
(cat test.csv ; echo) | while IFS=',' read -r last first
do
if [[ $last != "" ]] ; then
echo "$last $first"
fi
done
Another method relies on the fact that the values are being placed into the variables by the read but they're just not being output because of the while statement:
#!/bin/bash
while IFS=',' read -r last first
do
echo "$last $first"
done <test.csv
if [[ $last != "" ]] ; then
echo "$last $first"
fi
That one works without creating another subshell to modify the input to the while statement.
Of course, I'm assuming here that you want to do more inside the loop that just output the values with a space rather than a comma. If that's all you wanted to do, there are other tools better suited than a bash read loop, such as:
tr "," " " <test.csv
cat file |sed -e '${/^$/!s/$/\n/;}'| while IFS=',' read -r last first; do echo "$last $first"; done
If the last (unterminated) line needs to be processed differently from the rest, #paxdiablo's version with the extra if statement is the way to go; but if it's going to be handled like all the others, it's cleaner to process it in the main loop.
You can roll the "if there was an unterminated last line" into the main loop condition like this:
while IFS=',' read -r last first || [ -n "$last" ]
do
echo "$last $first"
done < test.csv

How to extract values from a string on ksh (korn shell)

I have the following input
MyComposite[2.1], partition=default, mode=active, state=on, isDefault=true, deployedTime=2012-05-07T15:35:22.473-07:00
MessageManager[1.0], partition=default, mode=active, state=on, isDefault=true, deployedTime=2012-05-07T15:37:14.137-07:00
SimpleApproval[1.0], partition=default, mode=active, state=on, isDefault=true, deployedTime=2012-05-07T15:28:39.599-07:00
and I have a script that parses the input line by line from a file but I don't have a clue on how I could extract individual parameters from each line into local variables so I can perform additional processes
So far I'm trying the following:
#!/bin/ksh
file="output"
compositeName="foo" ci=0
# while loop while read line do
# display line or do somthing on $line
if echo "$line" | egrep -q '\[[0-9]*\.[0-9]*\].*?(mode=active).*?
(state=on)' then compositeName=$( echo "$line" | egrep '[0-9]*' )
echo "$compositeName"
#echo "$line"
fi
done <"$file"
I'm somwhow lookint to extract only two values from this string, the first word and the float between brackets
ie:
name = MyComposite
version = 2.1
any ideas?
I'm not sure if those line numbers are in the file or not. If not, you can do this:
#!/usr/bin/env ksh
while IFS="," read nameVersion line; do
name="${nameVersion%%\[*}"
version="${nameVersion//*\[+([0-9.])\]*/\1}"
print "name=$name version=$version"
done < "$file"
If the line numbers are in the file, change the name assignment in the above script to name="${nameVersion//+([0-9]).+( )+(*)\[*/\3}"

Read user input inside a loop

I am having a bash script which is something like following,
cat filename | while read line
do
read input;
echo $input;
done
but this is clearly not giving me the right output as when I do read in the while loop it tries to read from the file filename because of the possible I/O redirection.
Any other way of doing the same?
Read from the controlling terminal device:
read input </dev/tty
more info: http://compgroups.net/comp.unix.shell/Fixing-stdin-inside-a-redirected-loop
You can redirect the regular stdin through unit 3 to keep the get it inside the pipeline:
{ cat notify-finished | while read line; do
read -u 3 input
echo "$input"
done; } 3<&0
BTW, if you really are using cat this way, replace it with a redirect and things become even easier:
while read line; do
read -u 3 input
echo "$input"
done 3<&0 <notify-finished
Or, you can swap stdin and unit 3 in that version -- read the file with unit 3, and just leave stdin alone:
while read line <&3; do
# read & use stdin normally inside the loop
read input
echo "$input"
done 3<notify-finished
Try to change the loop like this:
for line in $(cat filename); do
read input
echo $input;
done
Unit test:
for line in $(cat /etc/passwd); do
read input
echo $input;
echo "[$line]"
done
I have found this parameter -u with read.
"-u 1" means "read from stdout"
while read -r newline; do
((i++))
read -u 1 -p "Doing $i""th file, called $newline. Write your answer and press Enter!"
echo "Processing $newline with $REPLY" # united input from two different read commands.
done <<< $(ls)
It looks like you read twice, the read inside the while loop is not needed. Also, you don't need to invoke the cat command:
while read input
do
echo $input
done < filename
echo "Enter the Programs you want to run:"
> ${PROGRAM_LIST}
while read PROGRAM_ENTRY
do
if [ ! -s ${PROGRAM_ENTRY} ]
then
echo ${PROGRAM_ENTRY} >> ${PROGRAM_LIST}
else
break
fi
done

Resources