Two lines of code: printf
for the prompt, sed
for everything else.
You might consider not using read
at all. There are many standard utilities that may be used by themselves or in scripts. One utility that works for this is sed
, the stream editor.
printf 'Say something: '
sed -r 's/^[Mm]y name is (.+)/hi, \1/; tQ; s/.*/I didn\x27t understand that/; :Q; q'
That's it. That's all you need.
How It Works
printf
prints the prompt. No newline is appended.
sed
processes input line-by-line. When not supplied a filename, it reads from standard input, as does the read
command. In this case we are stopping after just the first line (like the read command does), which is what the q
at the end is for--it quits. In general, sed
is used to process multiple lines of input, often every line in a file, but here we only want one line.
Both Bash and Sed are languages and they both have a notion of commands. Each line above is a single Bash command, but within the single-quoted Sed script passed to sed
, there are multiple Sed commands.
The first Sed command is s/[Mm]y name is (.+)/hi, \1/
. It performs substitution (s/
). It searches:
^
- at the very beginning of the line
[Mm]
- for M
or m
y name is
- for that literal text, including the trailing space
(.+)
- for one or more (+
) of any character (.
). This is what we are taking to be the user's name. Because it is enclosed in parentheses, and this is the first occurrence of (
in the pattern, it is captured into the first group and accessible via the first backreference, \1
.
It replaces such text with:
hi,
- that literal text, including the trailing space
\1
- the text that matched .+
in the search pattern
The second Sed command is tQ
. You can also write that t Q
. It tests:
- if the match operation attempted by the preceding
s
command succeeded.
- If it did, it skips to the label
:Q
, which appears later.
That achieves the goal of skipping over the next command unless the user didn't enter usable input. The function of the next command is to inform the user that their input wasn't understood, after all.
The third Sed command is s/.*/I didn\x27t understand that/
. It searches for:
.*
- Zero or more characters. This always matches the entire line.
That provides a way to do nothing further with the input, and instead replace it all with the message we want to display:
I didn
- that literal text
\x27
- a '
character, since we are using '
in the shell to pass the whole script to sed
(otherwise it would be fine to include a literal '
in the Sed script)
t understand that
- that literal text
The label :Q
. Labels in Sed start with a :
but when they are branched to with the Sed command t
(see above) the :
is omitted. You can call this what you like, just change all t
commands that use it accordingly.
The final Sed command, q
. This quits sed
. Since this Sed command is always encountered after processing a line, no more than one line is ever processed.
Portability Considerations
The sed
command shown above is actually not portable to all Sed implementations because it uses two features that are not standard, but are instead provided specifically by GNU sed, the Sed implementation in most GNU/Linux systems including Ubuntu.
- The escape sequence
\x27
to mean '
(and hexadecimal escapes in general).
- Semicolons around labels. The
;
is generally just as good as a newline to split separate Sed commands, but some implementations don't allow it around labels.
If you actually want to fix these problems, so you can run the exact same command on other OSes like macOS and FreeBSD that don't have GNU Sed (unless you install it on them), then you can simply use:
sed -r 's/^[Mm]y name is (.+)/hi, \1/
tQ
s/.*/I didn'\''t understand that/
:Q
q'
This works because Bourne-style shells like Bash permit literal newlines to appear inside single quotes. The sequence '\''
ends quoting, supplies a '
that is itself separately quoted due to the immediately preceding \
character, and then resumes single quoting. This does still presume you are using a Bourne-style shell, though not necessarily bash
.
An alternative is to use the $'
'
syntax, which is not actually standardized, and not all Bourne-style shells support it, though several popular ones do. In this way you can still probably write it as a true one-liner while using only standard Sed features, though I urge you not to do it this way because it's more confusing:
sed -r $'s/^[Mm]y name is (.+)/hi, \\1/\ntQ\n s/.*/I didn\x27t understand that/\n:Q\nq'
Besides \x27
being replaced with '
, \\
is replaced with \
and \n
is replaced with a newline, causing sed
to receive the same Sed script as with the above multi-line command that you should use instead anyway.
My thanks go out to Zanna, who suggested t
might be used for this and helped me simplify the Sed script once it was written.
if [[ $sth = "my name is ralph" ]]
, but you ask toecho "Hi <NAME>"
... If it will only work for the name Ralph, doecho Ralph
, otherwise, change your script/question to show your intentions.