read -p regex validation - bash

I am attempting to add regex validation to read -p, for example a domain name.
Current Code:
read -p "Do Something": dosomething
echo working on $dosomething
Thank you for the update as-per validating email, I trying to figure out if it is possible to apply regex validation at all to read -p.

Use a loop:
email=""
until [[ $email =~ $regex ]] ; do
read -p 'Enter your email: ' email
done
The regex
(?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*|"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*")#(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\[(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?|[a-z0-9-]*[a-z0-9]:(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])+)\])
shown at EmailRegex.com isn't bash compatible, unfortunately.

Short answer: sure, this is something that read could hypothetically do. But bash's implementation of read does not have this feature, and I don't know of any POSIX-compatible shell that does, and POSIX itself does not include such a feature, although it seems like the kind of thing that an implementation could add as an extension if desired. If you like the feature enough, consider submitting a patch to bash to add it!
It's easy enough to build validation using read: just use a loop to read a value, check to see if it is valid, and if not, try again. You can write this as a bash function if you feel the need to do this on a regular basis.

Related

Access value of read input from within a function in Bash

I'm have a function wrapper for read command. I'm trying to automate most of input asking in my script by a function. Below is non-working code.
ask_input() {
_question="$1"
_thevar="$2"
_finalvar=$(eval $(echo $_thevar))
read -ep "${_question}: " "$_thevar"
printf "%s\n" "$_question" "$_thevar" "$_finalvar"
}
Basically what I'm hoping is when I execute following.
ask_input "Do you like apple (yes/no)" the_answer
If an user type yes, each variable will contain following (w/o quotes of course, it was just for easier readability).
$_question --> "Do you like apple (yes/no)"
$_thevar --> "the_answer"
$_finalvar --> "yes"
The eval command is my attempt to solve the problem, but I have not found the actual solution to this.
The main two things you need to change are: 1) use indirect expansion with ! (_finalvar="${!_thevar}") instead of messing with eval, and do that after reading something into that variable. I'd also recommend making all those variables local to the function. So something like this:
ask_input() {
local _question="$1"
local _thevar="$2"
read -ep "${_question}: " "$_thevar"
local _finalvar="${!_thevar}"
printf "%s\n" "$_question" "$_thevar" "$_finalvar"
}
Since those variables are local now, you could probably also remove the _ prefixes (unless you're worried about a conflict with the variable name supplied as $2).

Possible to get bash input while user is at prompt? (Essentially an event listener)

Old stuff:
Background:
- Ultimate goal is to put a script in my .bash_profile that warns me by changing text color if I'm typing a commit message and it gets too
long (yes I'm aware vim has something like this).
Progress:
- I found the read -n option which led me to write this:
while true; do
# This hits at the 53rd character
read -rn53 input
# I have commit aliased to gc so the if is just checking if I'm writing a commit
if [ "${input:0:2}" = "gc" ]; then
printf "\nMessage getting long"
fi
done
Question:
- However, running this takes the user out of the bash prompt. I need a way to do something like this while at a normal prompt. I can't find
information on anything like this. Does that mean it's not possible?
Or am I just going about it the wrong way?
New progress:
I found the bind -x option which led me to write this:
check_commit() {
if [ "${READLINE_LINE:0:13}" == 'git commit -m' ] && [ ${#READLINE_LINE} -gt 87 ]; then
echo "Commit is $((${#READLINE_LINE} - 87)) characters too long!"
fi
READLINE_LINE="$READLINE_LINE$1"
READLINE_POINT=$(($READLINE_POINT+1))
}
bind -x '"\"": check_commit "\""'
It listens for a double quote and if I'm writing a long commit message tells me how many characters I am over the limit. Also puts the character I typed into the current line since it is eaten by the bind.
New question:
Now I just need a way to put in a regex, character list or at least a variable instead of \" so I can listen on more keys (Yes, I'm aware bind -x probably wasn't intended to be used this way. I can check performance/footprint/stability myself). I tried "$char", "${char}", "$(char)" and a few other things, but none seem to work. What is the correct approach here ?
AFAIK, not possible in a sane way if you want this to happen during your normal prompt (when PROMPT_COMMAND and PS1 are evaluated). That would involved binding a custom compiled readline function for every insert-self and alike.
If you want this to happen in a script using prompt builtin, this is crudely possible with a loop of
read -e -i $(munge_buf $buf) -n $(buf_warn_len $buf) -p $(buf_warning $buf) buf
like commands. This will allow you to create munge_buf() to alter the currently typed text if needed, buf_warn_len() to calculate a new len to warn at (which may be very large if warning was already displayed), and buf_warn_msg() to derive a warning message based upon the buffer.

Unix script - String Validation

I'm very new to Unix scripting (.ksh). I have to implement a functionality to check whether my argument says "welcome" present in an string array e.g.
{"welcome","test","exit"}
The logic is similar to String.contains in Java.
Any help will be appreciated.
You can do something like this. Following is in bash, you need to change it accordingly to ksh.
script
array=(welcome test exit)
string='welcome';
for item in ${array[*]}
do
if [[ $string =~ .*$item.* ]]
then
echo "It's present!"
fi
done
Output
It's present!
To iterate over arguments passed to a shell script, use for with empty in, that default the iteration over arguments, or in '$#'.

shell scripting quotation

I have written a small script with which I take the name of a File.
#objectname
echo "objectname"
read ON
Can't get simpler.
I do some processing with the file I get.
gpg -c --no-use-agent "$ON"
For example if I have a file a.exe --> It will encrypt it and give me a file with a different md5 and an extension. Now, the file looks this way a.exe.gpg
Now, if I give it a bind the name of the file directly.
like this for example:
Taken from : this link
# This works
fileName='a.exe.gpg'
md5sum=$(md5sum ${fileName})
echo $md5sum
it returns it properly.
What if I want to do it dynamically.
This is what I tried:
#does not work
gpg -c --no-use-agent "$ON"
fileName= `$ON.gpg`
md5sum= $(md5sum ${fileName})
echo $md5sum
I get this bug here: upload.sh: 1: upload.sh: Fire.exe.gpg: not found and the program does not exit.
May I ask where exactly is the mistake I am doing?
The error is here:
fileName= `$ON.gpg`
There should be no space after =. (Also look at the next line.)
You used back-quotes, which execute $ON.gpg rather than simply evaluating it. Back-quotes are the same as $(...) but less elegant. Use double-quotes for this.
Read Greg's wiki entry on quotes for an ultra-detailed explanation with opinionated commentary. :-)
Be careful when making assignments in shell script. Don't use spaces in any sides of the operator=. Try the following:
fileName="$ON.gpg"
md5sum=$(md5sum ${fileName})
Note that the variable and the assignment operator= are together with no space.
Also, when you use backticks as `expression`, it will be executed by shell like using $(expression), as pointed by user ghoti.
You goofed on fixing the filename.
fileName="$ON.gpg"

BASH Expression to replace beginning and ending of a string in one operation?

Here's a simple problem that's been bugging me for some time. I often find I have a number of input files in some directory, and I want to construct output file names by replacing beginning and ending portions. For example, given this:
source/foo.c
source/bar.c
source/foo_bar.c
I often end up writing BASH expressions like:
for f in source/*.c; do
a="obj/${f##*/}"
b="${a%.*}.obj"
process "$f" "$b"
done
to generate the commands
process "source/foo.c" "obj/foo.obj"
process "source/bar.c "obj/bar.obj"
process "source/foo_bar.c "obj/foo_bar.obj"
The above works, but its a lot wordier than I like, and I would prefer to avoid the temporary variables. Ideally there would be some command that could replace the beginning and ends of a string in one shot, so that I could just write something like:
for f in source/*.c; do process "$f" "obj/${f##*/%.*}.obj"; done
Of course, the above doesn't work. Does anyone know something that will? I'm just trying to save myself some typing here.
Not the prettiest thing in the world, but you can use a regular expression to group the content you want to pick out, and then refer to the BASH_REMATCH array:
if [[ $f =~ ^source/(.*).c$ ]] ; then f="obj/${BASH_REMATCH[1]}.o"; fi
you shouldn't have to worry about your code being "wordier" or not. In fact, being a bit verbose is no harm, consider how much it will improve your(or someone else) understanding of the script. Besides, for performance, using bash's internal string manipulation is much faster than calling external commands. Lastly, you are not going to retype your commands every time you use it right? So why worry that its "wordier" since these commands are already in your script?
Not directly in bash. You can use sed, of course:
b="$(sed 's|^source/(.*).c$|obj/$1.obj|' <<< "$f")"
Why not simply using cd to remove the "source/" part?
This way we can avoid the temporary variables a and b:
for f in $(cd source; printf "%s\n" *.c); do
echo process "source/${f}" "obj/${f%.*}.obj"
done

Resources