How to Read from User Within While-Loop Read Line

How read line in while loop works

Let's analyze this fragment:

while read LINE; do
echo "This is a downvote"
done < inputfile

This is the compound command "while":

while TESTCOMMAND; do SOMETHING; done

(semicolons can be substituted by newlines).

First of all, TESTCOMMAND is a complete command, maybe composed of many words - this is why it is ended by a semicolon or a newline. For example, TESTCOMMAND coud be "tail file.txt | grep bye": these are many words, and actually two commands are run instead, with a pipe in between.

For every iteration, TESTCOMMAND is run and, if it succeeds, the commands between "do" and "done" (the SOMETHING part) are executed. If TESTCOMMAND fails, the loop is exited.

Now, let see the case when TESTCOMMAND is "read LINE". Before execution, the two words "read" and "LINE" simply mean what they are: two words, the first is "read" and the second is "LINE". The first word will be the name of the command to be executed; all the others will be argument for the command. So, you see, "LINE" is a word with no special meaning, it is not a variable - its content is not even evaluated (in order to evaluate it before the execution of the command, we would write "$LINE").

Then the command "read" is executed. It is that command, "read", that interprets its argument "LINE" as a variable name to write to. Other commands could interpret that word ("LINE") as a file name, or a textual word, or whatever they want.

The last line of the fragment, "done < inputfile" says that the command must have its (standard) input redirected: the command will not read the standard input (which in many cases is the console), but the file specified ("inputfile" in this case).

What is the command the redirection applies to? It is the while command, and all its "children". Do not get confused by the fact that the redirection symbol "<" seems far from the keyword "while". The shell sees "while", and knows that there is a corresponding "done" later.

--- update after comment request ---

More or less the execution of these lines:

while read LINE; do
echo "This is a downvote"
done < inputfile

goes like this (assuming that inputfile contains 3 lines):

  1. the shell finds the "while" and parses it until the "done"
  2. a redirection is found, so standard input is redirected
  3. the cycle begins
  4. "read LINE" is executed, variable FILE is filled with the first line read from inputfile, and the read command returns "success"
  5. ...so the body, "echo ..." is executed
  6. the "done" keyword restarts the cycle
  7. "read LINE" and "echo ..." is executed two more times
  8. when trying to read the fourth line from inputfile, the read command fails (end of file) and returns "fail"
  9. the "while" cycle breaks (jumps to finalization)
  10. ...the finalization says to reset the standard input
  11. the execution of the rest of the script continues.

How to read from user within while-loop read line?

You pipe data into your the while loops STDIN. So the read in get_ans is also taking data from that STDIN stream.

You can pipe data into while on a different file descriptor to avoid the issue and stop bothering with temp files:

while read -u 9 line; do
NAME=$(get_ans Name "$line")
done 9< list.txt

get_ans() {
local PROMPT=$1 DEFAULT=$2 ans
read -p "$PROMPT [$DEFAULT]: " ans
echo "${ans:-$DEFAULT}"
}

How can I prompt the user inside a while read loop?

Use a FD other than 0 (stdin), to leave the original stdin free for input from the user:

#!/usr/bin/env bash
# ^^^^- NOT /bin/sh; also, do not run with "sh scriptname"

while read -r line <&3; do
line=${line#remotes/origin/} # trim remotes/origin/ w/o needing sed
case $line in
*develop*|*master|*release/*|*hotfix*) continue ;;
*) read -r -p "Do you want to delete branch $line <y/N>?" prompt
echo "$prompt" ;;
esac
done 3< <(git branch -a)

Here, we're using FD 3 for output from git, such that FD 0 is still stdin, available to read from the user; and then redirecting <&3 on the explicit read where we want content from git.

Read user input inside a loop

Read from the controlling terminal device:

read input </dev/tty

more info: http://compgroups.net/comp.unix.shell/Fixing-stdin-inside-a-redirected-loop

How to read input inside a while loop while reading from a file?

Use a different file descriptor for the named file. You know where that data is coming from; you don't know where standard input might be redirected from, so leave it alone.

while IFS= read -r -u 3 repo   # Read from file descriptor 3
do
read -p "Do you want to delete $repo" ip # Read from whatever standard input happens to be
echo "$ip"
if [ "$ip" = "y" ]
then
#do something
fi
done 3< "$filename" # Supply $filename on file descriptor 3

-u is bash-specific, but I note you are already using another bash-specific feature, the -p option to read. The POSIX way to read from something other than standard input is IFS= read -r repo <&3 (which says, copy file descriptor 3 onto standard input for this command).

While Loop - Display lines of file based on user-input

Your code does not satisfy your requirement. You need to read each line of code into a variable and print it. Your while loop is only satisfy with user input value and you are not printing the file line at all. See the correct code below and see you mistakes. Hope this will help you:-

#!/bin/bash
#author = johndoe

LINE_NUM=1
read -p "How many lines from /c/Users/johndoe/files/helpme.sh would you like to see? " USERLINEINPUT
while read -r line
do
echo "$LINE_NUM:$line"
if [ $LINE_NUM -ge $USERLINEINPUT ]; then
break;
fi
((LINE_NUM++))
done < "/c/Users/johndoe/files/helpme.sh"

read command not executing in while read line loop

As @that other guy pointed out in a comment, the problem is that all of the read commands in the loop are reading from the args.conf file, not the user. The way I'd handle this is by redirecting the conf file over a different file descriptor than stdin (fd #0); I like to use fd #3 for this:

while read -u3 line; do
...
done 3< modules/$name/args.conf

(Note: if your shell's read command doesn't understand the -u option, use read line <&3 instead.)

There are a number of other things in this script I'd recommend against:

  • Variable references without double-quotes around them, e.g. echo $line instead of echo "$line", and < modules/$name/args.conf instead of < "modules/$name/args.conf". Unquoted variable references get split into words (if they contain whitespace) and any wildcards that happen to match filenames will get replaced by a list of matching files. This can cause really weird and intermittent bugs. Unfortunately, your use of $argline depends on word splitting to separate multiple arguments; if you're using bash (not a generic POSIX shell) you can use arrays instead; I'll get to that.

  • You're using relative file paths everywhere, and cding in the script. This tends to be fragile and confusing, since file paths are different at different places in the script, and any relative paths passed in by the user will become invalid the first time the script cds somewhere else. Worse, you aren't checking for errors when you cd, so if any cd fails for any reason, then entire rest of the script will run in the wrong place and fail bizarrely. You'd be far better off figuring out where your system's root directory is (as an absolute path), then referencing everything from it (e.g. < "$module_root/modules/$name/args.conf").

  • Actually, you're not checking for errors anywhere. It's generally a good idea, when writing any sort of program, to try to think of what can go wrong and how your program should respond (and also to expect that things you didn't think of will also go wrong). Some people like to use set -e to make their scripts exit if any simple command fails, but this doesn't always do what you'd expect. I prefer to explicitly test the exit status of the commands in my script, with something like:

    command1 || {
    echo 'command1 failed!' >&2
    exit 1
    }
    if command2; then
    echo 'command2 succeeded!' >&2
    else
    echo 'command2 failed!' >&2
    exit 1
    fi
  • You're creating temp files in the current directory, which risks random conflicts (with other runs of the script at the same time, any files that happen to have names you're using, etc). It's better to create a temp directory at the beginning, then store everything in it (again, by absolute path):

    module_tmp="$(mktemp -dt module-system)" || {
    echo "Error creating temp directory" >&2
    exit 1
    }
    ...
    echo "$answer" >> "$module_tmp/arglist.tmp"

    (BTW, note that I'm using $() instead of backticks. They're easier to read, and don't have some subtle syntactic oddities that backticks have. I recommend switching.)

  • Speaking of which, you're overusing temp files; a lot of what you're doing with can be done just fine with shell variables and built-in shell features. For example, rather than reading line from the config file, then storing them in a temp file and using cut to split them into fields, you can simply echo to cut:

    arg="$(echo "$line" | cut -d ";" -f 1)"

    ...or better yet, use read's built-in ability to split fields based on whatever IFS is set to:

    while IFS=";" read -u3 arg requ description; do

    (Note that since the assignment to IFS is a prefix to the read command, it only affects that one command; changing IFS globally can have weird effects, and should be avoided whenever possible.)

    Similarly, storing the argument list in a file, converting newlines to spaces into another file, then reading that file... you can skip any or all of these steps. If you're using bash, store the arg list in an array:

    arglist=()
    while ...
    arglist+=("$answer") # or ("#arg=$answer")? Not sure of your syntax.
    done ...

    "$module_root/modules/$name/$interpreter" "$file" "${arglist[@]}"

    (That messy syntax, with the double-quotes, curly braces, square brackets, and at-sign, is the generally correct way to expand an array in bash).

    If you can't count on bash extensions like arrays, you can at least do it the old messy way with a plain variable:

    arglist=""
    while ...
    arglist="$arglist $answer" # or "$arglist $arg=$answer"? Not sure of your syntax.
    done ...

    "$module_root/modules/$name/$interpreter" "$file" $arglist

    ... but this runs the risk of arguments being word-split and/or expanded to lists of files.

For loop not reading it per line

#!/bin/bash

input="data.txt"
while IFS= read -r line
do
read -ra array <<< "$line"
F1="${array[0]}"
F2="${array[1]}"
F3="${array[2]}"
F4="${array[3]}"
F5="${array[4]}"
F6="${array[5]}"

echo "$F1 $F2 $F3 $F4 $F5 $F6 "

done < "$input"

Reading file in while loop bash scripting

You could try something like :

#!/bin/bash

FILE="test.txt"

while IFS=":" read -a data; do
echo "${data[@]}"
if [[ $(echo ${data[5]}|egrep -c 'al-03-04') -eq 1 ]]; then
if [[ $(finger "${data[0]}" 2>&1) =~ "no such user" ]]; then
echo "${data[0]}. Inactive user"
else
echo "${data[0]}. Active user"
fi
fi
done < "$FILE"

Here's the output :

ineumann ~ $ cat test.txt 
ineumann:x:5214:1007:Javier Lopez,,,:/home/al-03-04/jfer:/bin/bash
jperez:x:10912:1009:Juan Perez,,,:/home/al-03-04/jperez:/bin/bash
mfernan:x:10913:1009:Manuel Fernandez,,,:/home/al-02-03/mfernan:/bin/bash
ineumann ~ $ ./test.sh
ineumann x 5214 1007 Javier Lopez,,, /home/al-03-04/jfer /bin/bash
ineumann. Active user
jperez x 10912 1009 Juan Perez,,, /home/al-03-04/jperez /bin/bash
jperez. Inactive user
mfernan x 10913 1009 Manuel Fernandez,,, /home/al-02-03/mfernan /bin/bash

A few comments on your script :

  • No need to use cat to read your file in a loop.
  • finger $(cut -d: -f1) 2> fich : cut need an input. And no need to use a temporary file to catch the output of finger (moreover this is not thread safe).
  • No need to use cut in your script when you choose the right IFS to split a line in multiple parts. In your case, I think the smartest choice would be :.
  • You can change the IFS only inside the loop with the syntax while IFS=':' read; do ...; done. No need to re-assign IFS with OLDIFS.
  • You can also use the while IFS=':' read var1 var2 var3 trash; do ...; done syntax to avoid to use an array with read -a (but I'd prefer to use an array as I wrote in my version of your script).


Related Topics



Leave a reply



Submit