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
Unix & Linux Stack Exchange is a question and answer site for users of Linux, FreeBSD and other Un*x-like operating systems. It only takes a minute to sign up.
Sign up to join this communityThe 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.
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 e
scape 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 n
ewline.
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 i
nsensitive 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.
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"