0

Can anyone explain what happens in this bash line step by step? I am new here and trying to understand how this code works, especially from echo:

read char; echo -e "YES\nNO\n" | grep -i $char
4

The commands on the line reads a string into the variable char, probably interactively from the user.

The echo+grep pipeline tries to determine whether the entered string is an affirmative or not. It does this by matching the words YES and NO against the entered string (with the entered string as the pattern), case insensitively. If the user enters an upper or lowercase character or substring present in the word YES, the result would be YES; if they entered an upper or lowercase character or substring present in the string NO, the result would be NO. Entering something like maybe would result in an empty output.

The downside with this approach is that if the user enters, for example, ., both YES and NO would match as grep would treat the dot as a regular expression which matches any character. Since $char is unquoted in the call to grep, it would also have the potential to cause a denial of service attack against the machine if the user enters a shell globbing pattern such as /*/*/*/*/../../../../*/*/*/* as input (example taken from Security implications of forgetting to quote a variable in bash/POSIX shells). You could also cause confusing output with the command by entering e.g. -r -o -e . / (would output every character of every non-binary file on a line by itself preceded by the pathname of the file that it was part of).

The code that you show is "strange and unusual" in that it uses user input as something that is essentially code, i.e. it uses the user input as a pattern, and tests static data against this variable pattern. This is the opposite of what one usually does, which is to take the input from the user and test this variable data against a static pattern.

It is more common to use code akin to the following:

read -p 'Yes/[N]o: ' yesno
if [[ $yesno == [Yy]* ]]; then
   # code for affirmative
else
   # code for non-affirmative
fi

The code above reads a string from the user and tests whether it starts with a y or a Y character. The first branch of the if statement would be taken if it does, but it would default to taking the else branch otherwise.

You could obviously also test on the whole word YES, or [Yy][Ee][Ss] for a case insensitive match, or do a proper input loop with verification:

while true; do
    read -p 'Yes/No: ' yesno

    if [ "$yesno" = Yes ] || [ "$yesno" = No ]; then
        break
    fi

    echo 'Please enter "Yes" or "No"' >&2
done

# $yesno is either Yes or No here

(or something similar).

Notice how both example codes above use the user's input exclusively as data and never as a pattern.

Rewriting your original command minimally to something a bit idiomatic (but functionally different, and probably not foolproof) would turn it into

read yesno; printf '%s\n' "${yesno^^}" | grep -i -w -E 'yes|no'

This would return uppercase YES or NO if the user typed yes or no. This is functionally different since it requires the user to type something more than just e for a yes, for example.

3

Well, let's take each command in turn:

read char

This will read from standard input (typically your keyboard but may be an indirected file or a piped stream; see below) and put the received data into a variable named char.

echo -e "YES\nNO\n"

echo will display to standard output (typically the terminal) the provided parameters. The -e switch will (often, but not always; echo is kind of problematic with regard to consistent implementation) allow you to escape certain characters to do some rudimentary formatting. In this case, it is using \n, which is is an escaped n, which is a shorthand for a newline.

grep -i $char

grep is a tool to search provided input to see if there are any matches for the provided pattern. The -i switch tells it to be a case insensitive search.

The | between the echo and grep commands is a "pipe". This connects the output of the first command to the input of the second, which means that grep will be searching this corpus for the pattern reflected in the contents of the variable char:

YES
NO

The practical result of this sequence of commands is to look at the provided input (assumed based on the variable name to be a single character, but this is not checked). If this character is a Y, E, S, y, e, or s, the output will be YES. If this character is an N, O, n, or o, the output will be NO. However, if (for instance) the input is yo or ne, then nothing will be output.

As earlier noted, the presumption that the input is one character is not checked. This is not the only antipattern in the given example command sequence. For example, variables are not being quoted; and a regular expression tool (grep) is being used without the input being screened for regex wildcards.

1
  • Thanks you for making this very understandable. I really appreciate it. Mar 11 '20 at 17:52
0

A "character" (rather: string) is read in from /dev/stdin . Meaning: you type it.

Then two lines are output to /dev/stdout. Meaning your console.

And via the pipe (the "|") this is then grep-ed for the case-insensitive version of your input.

This means if you input "bar" it will simply finish without any apparent output, but if you input, say, "y" then it will output the line "YES" most likely even highlighting the "Y"

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.