1

I'm making this script: when you type

"my name is [your name]"

it tells you

"hi, [your name]".

However, I don't know how to make [your name] anything you type. I've made a script with a particular name, but I want it to echo whatever name the user enters:

#!/bin/bash
read -p "Say something: " sth
  if [[ $sth = "my name is ralph" ]]
      then
          echo "Hi $(echo $sth | cut -f 4 -d ' ')"
  else
      echo "I didn't understand that"
  fi

So this will echo Hi ralph, but how to make it echo Hi [your name] with whatever name you typed?

1
  • if [[ $sth = "my name is ralph" ]], but you ask to echo "Hi <NAME>"... If it will only work for the name Ralph, do echo Ralph, otherwise, change your script/question to show your intentions.
    – M. Becerra
    Mar 17, 2017 at 17:22

6 Answers 6

3

The specifics aren't given by you but generally you capture name like this.

#!/bin/bash   

regex='[Mm]y(\ name\ is\ )(\w*)'

read -p "Say something: " response

echo $response

if [[ "$response" =~ $regex ]]; then

        name=$(echo $response | cut -d' ' -f4)

        echo "Hi $name"
else
        echo "I didn't understand that"

fi
0
3

You could use the regex test =~ to capture whatever comes after my name is:

$ read -rp "Say something: "; if [[ "$REPLY" =~ [Mm]y\ name\ is\ .* ]]; then echo "Hi "${REPLY:11}"" ; fi
Say something: my name is zanna
Hi zanna

Here I used a parameter expansion to remove the first 11 characters (my name is) and print whatever came after it, but if the user typed more than their name, the result might be not what you want:

Say something: my name is pixie and I eat flowers
Hi pixie and I eat flowers

George's answer deals with this using cut to print only the 4th field (but I guess the user might type My name is Super Rainbow Unicorn and you might not want the shell to reply only Hi Super).

more readably and with an else:

read -rp "Say something: "
if [[ "$REPLY" =~ [Mm]y\ name\ is\ .* ]]
  then 
    echo "Hi "${REPLY:11}""
  else
    echo "I didn't understand that."
fi
2

Zanna and George both did the right thing in using regexes, but then stopped short of actually using Bash's regex support to extract the name. With something like:

regex='[Mm]y(\ name\ is\ )(\w*)'
read -p "Say something: " response

if [[ "$response" =~ $regex ]]; then

After the [[ ]] regex test is done, bash makes available the regex groups matched in the BASH_REMATCH array. For example (with input My name is foo bar baz):

$ printf "%s\n" "${BASH_REMATCH[@]}"
My name is foo
 name is 
foo

So, modifying the groups a bit:

$ regex='My name is (\w.*)'
$ [[ "$response" =~ $regex ]]
$ printf "%s\n" "${BASH_REMATCH[@]}"
My name is foo bar baz
foo bar baz

Better still, you can tell bash to use case-insensitive regex matching:

shopt -s nocasematch

Combining all of this:

regex='My name is (\w.*)'
read -rp "Say something: "
shopt -s nocasematch
if [[ $REPLY =~ $regex ]]
then 
    echo "Hi ${BASH_REMATCH[1]}"
else
    echo "I didn't understand that."
fi

(Also, this lends itself quite easily to extracting the first word of a name, or the last.)

2

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.

  1. The escape sequence \x27 to mean ' (and hexadecimal escapes in general).
  2. 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.

0

Got this from other people's responses here, then did some modification of my own. This is just for a single, simple thing you can easily make into a command. There's no interpretation of the format, it won't protect against invalid entry, etc.

read -p username: username ; curl -s "https://api.github.com/users/$username/repos?per_page=100" | grep -o 'git@[^"]*'
-1

You are having a problem with your script because you are using the = operator which is making an assignment to your $sth variable rather than making a comparison between the two.

Use the the double equal symbol (==) in place of the single (=). The == is an operator for pattern match.

Change your conditional statement:

Change from:

if [[ $sth = "my name is ralph" ]]

Change to:

if [[ $sth == "my name is ralph" ]]
8
  • But what if the name is not ralph? That isn't what they want to test...
    – Zanna
    Mar 19, 2017 at 4:38
  • I was hoping that I explained what the details of using the operator and assignment. The structure the OP had appeared that he was trying to apply a condition statement. My answer was an attempt to help the user to see how to use the operator. He used a condition statement that would never change. Because of that, I'm sure it was giving the user confusion... continued Mar 19, 2017 at 4:48
  • ... Looking at the sample code it appeared that he was thinking whatever he was typing was not being assigned to his variable. If he corrected the condition statement he could notice that what the user typed was stored in his read variable sth. If he tested it correctly, he would realize that his assignment is working. The read -p is pitting what the user type into the variable sth. He said, how to I read and echo what the user typed? He had already read what the.. user typed. Now he can test what the user typed and echo what the user typed in the code he posted... Mar 19, 2017 at 4:51
  • @Zanna His question appeared clear to me. I answered what it appeared what he was saying, and tried to explain in my answer the part that I was answering for him. It appears from your answer that you are seeing something different in his question than what I was seeing. But he typed clearly the input he wanted to go into his code. If you test his code and put the input he said when I type: "my name is [your name]". His question might be interperted a little different to you. Mar 19, 2017 at 4:52
  • 3
    This answer is wrong. = in [[ or [/test does not perform assignment. Try it! Nov 18, 2017 at 6:46

You must log in to answer this question.