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
Related
I have a bash script that prompts the user for different information based on what they're trying to do. The prompts are usually done with read -p. Usually it works just fine, the user sees what is being asked, enters what they need to enter, and everything does what it needs to do.
See the following (sanitized) snippet of a function in the script:
#!/bin/bash
function_name() {
if [ "$this_value" == "default" ];then
echo "Value set to default."
read -p "Enter desired value here: " desired_value
desired_value=${desired_value^^}
if [ "${#desired_value}" != 3 ] ;then
echo "$desired_value is an invalid entry."
exit 1
fi
if [ "$desired_value" != "$(some command that returns something to compare against)" ];then
echo "$desired_value is an invalid entry."
exit 1
fi
read -p "You entered $desired_value. Is this correct? [y/N] " reply
reply=${reply,,}
case "$reply" in
y|yes)
$some command that does what I want it to do
;;
*)
echo "User did not enter yes"
exit 1
;;
esac
fi
}
Usually the Enter desired value here and is this correct? lines appear just fine. But in a few instances I've seen, for some reason the read prompt is just blank. A user will see the following:
./script.bash
##unrelated script stuff
##unrelated script stuff
Value set to default.
user_entered_value_here
User did not enter yes. Exiting.
This is a real example that just happened that finally made me come here to ask what is going on (and I modified appropriately to make it an SO post).
What's happening is these two blank lines appear instead of the read -p text. For the first one, the user entered user_entered_value_here because they already know what is supposed to be entered there even without the read prompt. The second one, the Y/N prompt, they don't know, so they see it apparently hanging, and hit Enter instead of y, causing it to trigger the * case option.
I don't understand why the read -p text is not appearing, and especially why it's appearing for most users but not all users. I suspect there's some kind of environmental setting that causes this, but for the life of me I can't figure out what. This is being run only on RHEL 6.2, under bash 4.1.2.
I looked at the man of bash to catch some kind of detail about the read built-in. It is specified that -p option displays the "prompt on standard error, without a trailing newline, before attempting to read any input. The prompt is displayed only if input is coming from a terminal".
Let's consider the simple script input.sh:
#!/bin/bash
read -p "Prompt : " value
echo The user entered: "$value"
Example of execution:
$ ./input.sh
Prompt : foo
The user entered: foo
If stderr is redirected:
$ ./input.sh 2>/dev/null
foo
The user entered: foo
If the input is a pipe
$ echo foo | ./input.sh
The user entered: foo
If the input is a heredoc
$ ./input.sh <<EOF
> foo
> EOF
The user entered: foo
Rewrote your script with shell agnostic grammar and fixed some errors like comparing the string length with a string comparator != = rather than a numerical comparator -ne -eq:
#!/usr/bin/env sh
this_value=default
toupper() {
echo "$1" | tr '[:lower:]' '[:upper:]'
}
function_name() {
if [ "$this_value" = "default" ]; then
echo "Value set to default."
printf "Enter desired value here: "
read -r desired_value
desired_value=$(toupper "$desired_value")
if [ "${#desired_value}" -ne 3 ]; then
printf '%s is an invalid entry.\n' "$desired_value"
exit 1
fi
if [ "$desired_value" != "$(
echo ABC
: some command that returns something to compare against
)" ]; then
echo "$desired_value is an invalid entry."
exit 1
fi
printf 'You entered %s. Is this correct? [y/N] ' "$desired_value"
read -r reply
reply=$(toupper "$reply")
case $reply in
'y' | 'yes')
: "Some command that does what I want it to do"
;;
*)
echo "User did not enter yes"
exit 1
;;
esac
fi
}
function_name
How can I create a specific line in another file using bash please? Like
echo "Please input the days you want to keep "
$key= ?
touch .beebrc; keep="$key"
where the file ".beebrc" has a line 'keep= x' and "$key" is created in the main script.
But how do I define "$key" please? And write it into ".beebrc" as a new line at position/line 8? The full function is -
function trim {
echo;
read -t "$temi" -n1 -p ""$bldgrn" Do you want to delete some of your download history? [y/n/q/r] $(tput sgr0)" ynqr ;
case "$ynqr" in
[Yy]) echo
read -t "$temi" -n3 -p ""$bldgrn" Please input the days you want to keep $(tput sgr0)" key ## ask
if test -e .beebrc && grep -q "^keep=" .beebrc 2>/dev/null ; then
sed -i "s/^keep=.*/keep=$key/" .beebrc
else
echo "keep=$key" >> .beebrc
#fi
cd /home/$USER/.get_iplayer
eval "$player" --trim-history "$key"; cd; ques;
#echo;;
[Nn]) ques;;
[Qq]) endex;;
[Rr]) exec "$beeb";;
* ) echo ""$bldgrn" Thank you $(tput sgr0)";;
esac
fi
};
Does this help in defining it all? (Sorry, should've put it in at first)
Perhaps:
read -p "Please input the days you want to keep: " key ## Ask.
echo "keep=\"$key\"" > .beebrc ## Store.
Use read to capture user input into a variable, and then write it to your file.
For example:
echo "Please input the days you want to keep "
read key
echo $key > .beebrc
#!/bin/bash
read -p "Please input the days you want to keep: " key
if test -e .beebrc && grep -q "^keep=" .beebrc 2>/dev/null ; then
sed -i "s/^keep=.*/keep=$key/" .beebrc
else
echo "keep=$key" >> .beebrc
fi
This script:
Prompts for input and stores the value in $key
Tests if .beebrc exists and that a line beginning "keep=" exists in it. If so, replace the keep= line with keep=$key
Otherwise append a new line/create the file with keep=$key.
This will need validation added because user input should not be trusted. (this answer might help)
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.)
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've been trying to send some AT commands to my modem and want to capture response into a variable. Here's my code:
exec 3<>/dev/ttyUSB3
echo -e "AT+CGSN\n" >&3
cat <&3
#read -r RESPONSE <&3
#echo "Response was $RESPONSE"
exec 3<&-
exec 3>&-
Results:
$ ./imei_checker.sh
AT+CGSN
356538041935676
OK
AT+CGSN
356538041935676
OK
But if I change cat to read, it doesn't work:
$ ./imei_checker.sh
Response was AT+CGSN
2 more questions:
Why it show the dupplicate output?
How do I close the file handle properly? exec 3<&- and exec 3>&-
seems doesn't work. I have to press Ctrl+C to
get control of the Terminal back.
read will only read a single line, unlike cat which will basically read and echo until end of file.
For a read version, you're best off reading with a timeout up until the point you get the OK (and storing any line that contains a lot of digits).
I think you'll find that it's not the closing of the number 3 file handle that's stopping things - it's more likely to be the cat which will continue to read/echo until an end-of-file event that isn't happening.
You can be certain of this if you just put:
echo XYZZY
immediately before the closing exec statements. If it's still in the cat, you'll never see it.
So, using a looping read version will probably fix that as well.
By way of example, here's how you can use read to do this with standard input:
#!/bin/bash
NUM=
while true ; do
read -p "> " -t 10 -r RESP <&0
if [[ $? -ge 128 ]] ; then RESP=OK ; fi
echo "Entered: $RESP"
if [[ $RESP = OK ]] ; then break ; fi
if [[ $RESP =~ ^[0-9] ]] ; then NUM=$RESP ; fi
done
echo "Finished, numerics were: '$NUM'"
It uses the timeout feature of read to detect if there's no more input (setting the input to OK so as to force loop exit). If you do get an OK before then, it exits normally anyway, the timeout simply caters for the possibility that the modem doesn't answer as expected.
The number is set initially to nothing but overwritten by any line from the "modem" that starts with a number.
Two sample runs, with and without an OK response from the "modem":
pax> ./testprog.sh
> hello
Entered: hello
> 12345
Entered: 12345
> OK
Entered: OK
Finished, numerics were: '12345'
pax> ./testprog.sh
> hello
Entered: hello
> now we wait 10 secs
Entered: now we wait 10 secs
> Entered: OK
Finished, numerics were: ''
It wouldn't be too hard to convert that to something similar with you modem device (either read <&3 or read -u3 will work just fine).
That would basically translate to your environment as:
exec 3<>/dev/ttyUSB3
echo -e "AT+CGSN\n" >&3
NUM=
while true ; do
read -t 10 -r RESP <&3
if [[ $? -ge 128 ]] ; then RESP=OK ; fi
echo "Entered: $RESP"
if [[ $RESP = OK ]] ; then break ; fi
if [[ $RESP =~ ^[0-9] ]] ; then NUM=$RESP ; fi
done
echo "Finished, numerics were: '$NUM'"
exec 3<&-
exec 3>&-
Now I haven't tested that since I don't have a modem hooked up (been on broadband for quite a while now) but it should be close to what you need, if not exact.
read takes the descriptor to read from as the argument following -u. See help read for details.
If you want to get the individual lines into variables I'd suggest to wrap read into a while:
while read -r RESPONSE <&3; do
echo "Response was $RESPONSE"
## e.g.:
[ "$RESPONSE" = "OK" ] && break
done
However, if you want "everything" that is sent back to you to reside in $RESPONSE you could do it like this:
RESPONSE="$(cat <&3)"