I am trying to run a read command inside a while loop to get user input as shown below:
for dir in ./*; do
for subdir in $dir/*; do
someprocess |
sort_processed_pipedout |
tail_sortedout |
while read line; do
another_process on $line
read -t 1 -n 10000 discard
read -u 3 -p "Save as final? (y/n)" USER_INPUT
if [ "$USER_INPUT" = y ]; then
something_else
echo "success"
fi
done 3<&0
done
done
This is what I think I am doing:
For every line of the tail output (basically one filename per line) the first read ignores any inadvertent input, second read switches the 'file handle' to 3 from 0 (as given here) and the rest proceeds as usual.
Here's what happens:
USER_INPUT seems to come directly from the tail output.
Why does this fail?
EDIT : How do I make it output to the same sub shell?
See BashFAQ #24 -- and if you want to redirect stdin, you need to do so earlier, while it's still pointing to the terminal, not after it's pointing to the output from the pipeline. Better not to do that at all, though, and just put what's otherwise your pipeline on a separate FD:
for subdir in ./*/*; do
while read -r -u 3 line; do
: another_process on "$line"
read -t 1 -n 10000 discard
read -p "Save as final? (y/n)" user_input
if [[ $user_input = y ]]; then
: something_else
echo "success"
fi
done 3< <(someprocess | sort_processed_pipeout | tail_sortedout)
done
0 represents the current standard input, not necessarily the terminal, so you are still just copying the pipe from tail to file descriptor 3. Move the redirection down to the next loop.
for dir in ./*; do
for subdir in $dir/*; do
someprocess |
sort_processed_pipedout |
tail_sortedout |
while read line; do
another_process on $line
read -t 1 -n 10000 discard
read -u 3 -p "Save as final? (y/n)" USER_INPUT
if [ "$USER_INPUT" = y ]; then
something_else
echo "success"
fi
done
done 3<&0
done
(Charles Duffy's answer has some more nice improvements, so I would go with that instead of this minimal fix.)
Related
I'm downloading files from a remote server. This part works fine but now I'm trying to compare the remote file size to the local file size.
If the two don't match then I want to prompt the user to enter yes or no
I've added the read command, but the script never pauses and asks the question. why ?
This is my test code
while IFS=',' read -r downloadfiles; do
case "$downloadfiles" in
AA)
filetoget="$downloadfiles.tar.gz"
;;
BB)
filetoget="$downloadfiles.zip"
;;
esac
sizeoffile=`curl -sI "http://server.com/$filetoget" | awk '/Content-Length/{sub("\r","",$NF); print $NF}'`
curl -O http://server.com/$filetoget
localsizeoffile=`stat --print="%s" $filetoget`
if [ "$localsizeoffile" -ne "$sizeoffile" ]; then
echo "error..."
read -p "Continue (y/n)?" CONT
if [ "$CONT" = "y" ]; then
echo "yaaa";
else
echo "booo";
fi
fi
done < filelist
Can anyone advise what I've done wrong. thanks
Update..
I've intentionally set it so a local file will have the wrong size so I can test.. I get the error error... but not the prompt asking if they want to continue.. any ideas
Fixed Typo
You can use this (inspired by dank's answer):
read -p "Continue (y/n)?" CONT </dev/tty
That's because the read inside the loop will also read from standard input, which was redirected from filelist. A standard way (in Bash) is to use another file descriptor for the redirection of filelist:
# Read from file descriptor 10: see end of loop, 10 is the redirection of filelist
while IFS=, read -u 10 -r downloadfiles; do
# ...
if (( localsizeoffile != sizeoffile )); then
echo "error..."
# This will read from standard input
read -p "Continue (y/n)?" cont
if [[ $cont = y ]]; then
echo "yaaa"
else
echo "booo"
fi
fi
# Redirect filelist to file descriptor 10
done 10< filelist
Please explain to me why the very last echo statement is blank? I expect that XCODE is incremented in the while loop to a value of 1:
#!/bin/bash
OUTPUT="name1 ip ip status" # normally output of another command with multi line output
if [ -z "$OUTPUT" ]
then
echo "Status WARN: No messages from SMcli"
exit $STATE_WARNING
else
echo "$OUTPUT"|while read NAME IP1 IP2 STATUS
do
if [ "$STATUS" != "Optimal" ]
then
echo "CRIT: $NAME - $STATUS"
echo $((++XCODE))
else
echo "OK: $NAME - $STATUS"
fi
done
fi
echo $XCODE
I've tried using the following statement instead of the ++XCODE method
XCODE=`expr $XCODE + 1`
and it too won't print outside of the while statement. I think I'm missing something about variable scope here, but the ol' man page isn't showing it to me.
Because you're piping into the while loop, a sub-shell is created to run the while loop.
Now this child process has its own copy of the environment and can't pass any
variables back to its parent (as in any unix process).
Therefore you'll need to restructure so that you're not piping into the loop.
Alternatively you could run in a function, for example, and echo the value you
want returned from the sub-process.
http://tldp.org/LDP/abs/html/subshells.html#SUBSHELL
The problem is that processes put together with a pipe are executed in subshells (and therefore have their own environment). Whatever happens within the while does not affect anything outside of the pipe.
Your specific example can be solved by rewriting the pipe to
while ... do ... done <<< "$OUTPUT"
or perhaps
while ... do ... done < <(echo "$OUTPUT")
This should work as well (because echo and while are in same subshell):
#!/bin/bash
cat /tmp/randomFile | (while read line
do
LINE="$LINE $line"
done && echo $LINE )
One more option:
#!/bin/bash
cat /some/file | while read line
do
var="abc"
echo $var | xsel -i -p # redirect stdin to the X primary selection
done
var=$(xsel -o -p) # redirect back to stdout
echo $var
EDIT:
Here, xsel is a requirement (install it).
Alternatively, you can use xclip:
xclip -i -selection clipboard
instead of
xsel -i -p
I got around this when I was making my own little du:
ls -l | sed '/total/d ; s/ */\t/g' | cut -f 5 |
( SUM=0; while read SIZE; do SUM=$(($SUM+$SIZE)); done; echo "$(($SUM/1024/1024/1024))GB" )
The point is that I make a subshell with ( ) containing my SUM variable and the while, but I pipe into the whole ( ) instead of into the while itself, which avoids the gotcha.
#!/bin/bash
OUTPUT="name1 ip ip status"
+export XCODE=0;
if [ -z "$OUTPUT" ]
----
echo "CRIT: $NAME - $STATUS"
- echo $((++XCODE))
+ export XCODE=$(( $XCODE + 1 ))
else
echo $XCODE
see if those changes help
Another option is to output the results into a file from the subshell and then read it in the parent shell. something like
#!/bin/bash
EXPORTFILE=/tmp/exportfile${RANDOM}
cat /tmp/randomFile | while read line
do
LINE="$LINE $line"
echo $LINE > $EXPORTFILE
done
LINE=$(cat $EXPORTFILE)
I have a problem, and I am pretty new to writing bash. I am parsing a csv file, checking for a few things. If a check is true, change the variable which will later be written to a file. I am reading an input file and outputting to a file as well, and If a certain argument checks True, then I want to prompt the user and pause the script until the user verifies the information matches (manual verification).
I have my most recent attempt which is not prompting. It just continues to read and write to the output. I am pretty sure because the output is going directly to my output file, but I do not know a way to direct the prompt to the terminal window which is where I am stuck.
INPUT=$TMPSAVE
IFS=,
[ ! -f $INPUT ] && { echo "$INPUT file not found"; exit 99; }
while read cdwon cdwod manu_date hp_sn manu_sn wiped_by wiped_date checked_by disposition readonly
do
for i in ${!allassigned[#]}
do
if [[ -n $manu_sn ]]
then
if echo ${allassigned[i]} | grep -q $manu_sn
then
physicaldrive=${allassigned[i-1]}
disk=$(hpacucli ctrl slot=${SLOT} show config detail | grep -B 4 ${physicaldrive} | head -1 | awk '{print $NF}');
if [[ -n $disk ]]; then #proceed to wipe drive
mount ${disk}${PRIMARY} ${MOUNT}
if [ -e $DIR ]; then
####### the file exists, now what to do with it? Automatcially prompt user?
cat $DIR > /dev/tty
echo "Does the drive serial number (${allassigned[i]}) match what was provided from the database ($manu_sn)? (y/n)" > /dev/tty
read
if [ "$REPLY" == "Y" ] || [ "$REPLY" == "y" ] || [ "$REPLY" == "YES" ] || [ "$REPLY" == "yes" ]; then
checked_by=$username
checked_bydate=`date`
fi
fi
fi
fi
fi
done
echo "$cdwon,$cdwod,$manu_date,$hp_sn,$manu_sn,$wiped_by,$wiped_date,$checked_by,$disposition,$readonly";
continue;
done < $INPUT > $OUTPUT
I solved my own issue here. I found out that read by default is reading from the stdin. When I was trying to prompt for input it was using stdin, so the lines I was reading were technically the stdin input. If you want to read a file in a while loop with the method I have done you have to change the fd like so:
while read -u 6 column1 column2
do
.....body
done 6< $INPUTFILE
Key being the "6" which could be any arbitrary number.
I use a command like to cat a pipe file and grep some data. A simple code such as,
temp=""
temp=$(cat file|grep "some data"| wc -c)
if [ $temp -gt 0 ]
then
echo "I got data"
fi
The file is a pipe(FIFO), it will output data and not stop. How can i to terminate the command of cat pipe in a finite time?
grep|wc is the wrong tool for this job. Choose a better one, such as sed,
if sed -n -e '/some data/q;$q1' file; then
....
fi
awk,
found=$(awk '/some data/{print"y";exit}' file)
if [ -n "$found" ]; then
....
fi
or sh itself.
found=
while read line; do
if expr "$line" : ".*some data" >/dev/null; then
found=y
break
fi
done <file
if [ -n "$found" ]; then
....
fi
I got it adding $ to temp variable in line 3:
if [ $temp -gt 0 ]
Because you want to compare temp value, and you get it using $ before the variable.
About file "pipe", you can execute cat until you get a specific string.
I mean, you can use cat for reading and stop when you receive, for example, a "\n".
I will give you an example that you can run in your terminal:
cat > example_file.txt << EOF
hello
I'm a example filen
EOF
cat will be reading from standard input untill you enter "EOF". And then, the content of the file will be:
cat example_file.txt
hello
I'm an example file
So this way you can read by chunks, for example, lines.
Just check the exit status of grep itself:
if grep -q "some data" file; then
echo "I got data"
fi
The -q prevents anything from being written to standard output if a match is found.
Another way to do it is by using shell script.
cat <some file and conditions> &
< perform your task>
kill $(pidof cat)
This works as long as you have one instance of "cat" running at a time.
You can use timeout command, which is part of coreutils.
man timeout:
NAME
timeout - run a command with a time limit
SYNOPSIS
timeout [OPTION] DURATION COMMAND [ARG]...
...
To wait 10 seconds:
temp=$(timeout 10 cat file|grep "some data"| wc -c)
if [ $temp -gt 0 ]
then
echo "I got data"
fi
Please explain to me why the very last echo statement is blank? I expect that XCODE is incremented in the while loop to a value of 1:
#!/bin/bash
OUTPUT="name1 ip ip status" # normally output of another command with multi line output
if [ -z "$OUTPUT" ]
then
echo "Status WARN: No messages from SMcli"
exit $STATE_WARNING
else
echo "$OUTPUT"|while read NAME IP1 IP2 STATUS
do
if [ "$STATUS" != "Optimal" ]
then
echo "CRIT: $NAME - $STATUS"
echo $((++XCODE))
else
echo "OK: $NAME - $STATUS"
fi
done
fi
echo $XCODE
I've tried using the following statement instead of the ++XCODE method
XCODE=`expr $XCODE + 1`
and it too won't print outside of the while statement. I think I'm missing something about variable scope here, but the ol' man page isn't showing it to me.
Because you're piping into the while loop, a sub-shell is created to run the while loop.
Now this child process has its own copy of the environment and can't pass any
variables back to its parent (as in any unix process).
Therefore you'll need to restructure so that you're not piping into the loop.
Alternatively you could run in a function, for example, and echo the value you
want returned from the sub-process.
http://tldp.org/LDP/abs/html/subshells.html#SUBSHELL
The problem is that processes put together with a pipe are executed in subshells (and therefore have their own environment). Whatever happens within the while does not affect anything outside of the pipe.
Your specific example can be solved by rewriting the pipe to
while ... do ... done <<< "$OUTPUT"
or perhaps
while ... do ... done < <(echo "$OUTPUT")
This should work as well (because echo and while are in same subshell):
#!/bin/bash
cat /tmp/randomFile | (while read line
do
LINE="$LINE $line"
done && echo $LINE )
One more option:
#!/bin/bash
cat /some/file | while read line
do
var="abc"
echo $var | xsel -i -p # redirect stdin to the X primary selection
done
var=$(xsel -o -p) # redirect back to stdout
echo $var
EDIT:
Here, xsel is a requirement (install it).
Alternatively, you can use xclip:
xclip -i -selection clipboard
instead of
xsel -i -p
I got around this when I was making my own little du:
ls -l | sed '/total/d ; s/ */\t/g' | cut -f 5 |
( SUM=0; while read SIZE; do SUM=$(($SUM+$SIZE)); done; echo "$(($SUM/1024/1024/1024))GB" )
The point is that I make a subshell with ( ) containing my SUM variable and the while, but I pipe into the whole ( ) instead of into the while itself, which avoids the gotcha.
#!/bin/bash
OUTPUT="name1 ip ip status"
+export XCODE=0;
if [ -z "$OUTPUT" ]
----
echo "CRIT: $NAME - $STATUS"
- echo $((++XCODE))
+ export XCODE=$(( $XCODE + 1 ))
else
echo $XCODE
see if those changes help
Another option is to output the results into a file from the subshell and then read it in the parent shell. something like
#!/bin/bash
EXPORTFILE=/tmp/exportfile${RANDOM}
cat /tmp/randomFile | while read line
do
LINE="$LINE $line"
echo $LINE > $EXPORTFILE
done
LINE=$(cat $EXPORTFILE)