Bourne Shell: Special characters Input Prevention

Environment: Sun UNIX

Language: Bourne Shell

Problem:
I am writing a script that allows user to key in a directory. Example: /root/tmp.

Since the user can key in anything he/she wants, I need to validate to make sure that he/she does not key in anything funny i.e. #@!*&^$><,."';:}[]+=)(|\%~`

Only "/", "-", "_", alphabet and numeric are the valid characters.

Initially, I wrote the code as follows but it does not work for certain special characters such as ")", "(", "\" and "`" (there could be more that I may have missed). Basically, it complained that the script had syntax error.

case "$b" in
*@) echo "Error: It has @";;
*~
) echo "Error: It has ~";;
*#*) echo "Error: It has #";;
*\**) echo "Error: It has *";;
*) echo "Directory is ok";;
esac

I believe there is a better way to code than listing everything in a case statement.

Hope someone can suggest a better solution. Thanks.

if you are checking for the path that exists

if [ -e $path ] ;then
 # Pass
else
 # Not valid path
fi

or

echo $path | awk '/[<>@*]/' 

and check for status

No, I am not checking for valid path.

In my script, the user is required to key in a path. The path input by the user will be created.

My problem is in the validation of the path input by user.

Here's my more detailed script if it helps:

while :
do
echo "Please enter the directory: \c"
read b
if [ "$b" = "" ]; then
echo "ERROR: Directory should not be blank!"
else
# Validation: more condition is required. This is an area I need help
case "$b" in
*@) echo "Error: It has @";;
*~
) echo "Error: It has ~";;
*#*) echo "Error: It has #";;
*\**) echo "Error: It has *";;
*) echo "Directory is ok"
break;;
esac
fi
done

Why dont you reverse your logic ? Look for the characters you need. Anything else will result in failure. You will have to enable the -noglob flag.

I have thought about it too but my skill in Bourne shell is limited and I could not figure out how I can do it. I have the following codes on my mind but I could not code something that is good enough to prevent the unwanted special characters from being entered by user:

# This will prevent user from entering /, _ & - which should be allowed
echo $b | grep "[^0-9]" > /dev/null 2>&1
if [ "$?" -eq "0" ]; then
echo "ERROR: Invalid Directory!"
fi

# This will prevent user from entering /, _ & - which should be allowed
echo $b | grep "[^a-z]" > /dev/null 2>&1
if [ "$?" -eq "0" ]; then
echo "ERROR: Invalid Directory!"
fi

# This will prevent user from entering /, _ & - which should be allowed
echo $b | grep "[^A-Z]" > /dev/null 2>&1
if [ "$?" -eq "0" ]; then
echo "ERROR: Invalid Directory!"
fi

How about just this sed statement.

sed -n -e "s+^[a-zA-Z0-9/_\-]*$+&+p"
[/tmp]$ echo 'ab!cada' | sed -n -e "s+^[a-zA-Z0-9/_\-]*$+&+p" 
[/tmp]$ echo 'ab21212cada' | sed -n -e "s+^[a-zA-Z0-9/_\-]*$+&+p"
ab21212cada
[/tmp]$ echo 'ab2121/2cada' | sed -n -e "s+^[a-zA-Z0-9/_\-]*$+&+p"
ab2121/2cada
[/tmp]$ echo 'ab2-_121/2cada' | sed -n -e "s+^[a-zA-Z0-9/_\-]*$+&+p"
ab2-_121/2cada

Not extensively tested.

Then search for anything containing a character which is not any of those.

case $b in *[!/-_A-Za-z0-9]*) echo error >&2;; esac

Thanks a lot for helping. I will try out all the proposed solution and update you.

I wrote the code as follows. I purposely create an infinite loop to re-enter the directory:

while :
do
echo "Enter directory: \c"
read b
case $b in
*[!/-_A-Za-z0-9]*) echo "Error" >&2;;
[!/-_A-Za-z0-9]) echo "Error 1" >&2;; # For experiment only
[!/-_A-Za-z0-9]
) echo "Error 2" >&2;; # For experiment only
[!/-_A-Za-z0-9]) echo "Error 3" >&2;; # For experiment only
esac
done

The result looks unpredictible to me. The ones with wrong result is indicated by "[Wrong]". They either fail to detect special character or treat valid ones as invalid. See the output below:

Enter directory: 12@ [Wrong]
Enter directory: 12@12 [Wrong]
Enter directory: 12#3]
Error
Enter directory: 12`76
Error
Enter directory: 12\87 [Wrong]
Enter directory: 12/778
Enter directory: 12%87
Error
Enter directory: 67)
Error
Enter directory: *(&*
Error
Enter directory: !76h
Error
Enter directory: (12
Error
Enter directory: `344
Error
Enter directory: 788'
Error
Enter directory: 87"+=
Error
Enter directory: 12_767
Enter directory: 788- [Wrong]
Error
Enter directory: 67-878 [Wrong]
Error
Enter directory: :78 [Wrong]
Enter directory: 878; [Wrong]
Enter directory: gha.js
Error
Enter directory: hg.
Error
Enter directory: |78
Error
Enter directory: {98}^H
Error

sed method does not seem to work either:

> echo 'ab!cada' | sed -n -e "s+^[a-zA-Z0-9/\-]*$+&+p"
cada: Event not found.
> echo 'ab21212cada' | sed -n -e "s+^[a-zA-Z0-9/
\-]$+&+p"
Illegal variable name.
> echo 'ab2121/2cada' | sed -n -e "s+^[a-zA-Z0-9/_\-]
$+&+p"
Illegal variable name.
> echo 'ab2-121/2cada' | sed -n -e "s+^[a-zA-Z0-9/\-]*$+&+p"
Illegal variable name.

Please post the output of uname -a


  1. a-zA-Z0-9/_\- ↩︎

  2. a-zA-Z0-9/_\- ↩︎

  3. a-zA-Z0-9/_\- ↩︎

  4. a-zA-Z0-9/_\- ↩︎

> uname -a
SunOS dsu1a 5.9 Generic_118558-19 sun4u sparc SUNW,Netra-240

The case statement works for me in bash.

You could try using caret ^ instead of exclamation mark ! to signify "not" in the character class, although I would be mildly surprised if that works.

I got syntax error after replacing ! with ^

./bb.sh: syntax error at line 21: `^' unexpected

Right, sorry for leading you astray, it was a long shot anyway.

If you go back to an individual character in each class, does that help at all?

case $b in
  *[@!]*) echo has @ in a character class containing exclamation mark also;;
  *[@]*) echo has @ in character class;;
  *@*) echo has @;;
esac

You might want to fill in more cases to see what exactly works and how.

Era, there's no need to apologise. You're just trying to help. I would go astray anyway while trying to find my way to the solution :slight_smile: It would probably be worse if I am doing on my own without any help from you. I will update you again after trying your new suggestion. Thanks

Era, I have just remembered the problem of having an individual character in each case. I cannot key in cases as follows because I encountered syntax errors:

*`*) echo "Error";;

*\*) echo "Error";;

*)*) echo "Error";

*(*) echo "Error";

I tried adding "\" in front of each of the character, it failed too. I tried with *\`* but it failed to detect string with 678\989

Yes, you need to backslash-escape (or quote) any characters with special meaning.

Backslash in particular is special when reading too, but if you can just get the others to work, that's a start.

*\`* should match 678`989 not 678\989

Merely quoting closing parenthesis is insufficient, because it has a special meaning in the syntax of the case statement, and quoting won't help the parser, so you need the backslash specifically.

case $b in
  *\\*) echo backslash;;  # don't necessarily expect this to work
  *\)*) echo close paren;;  # can't just quote this one, either
  *'('*) echo open paren;;
  *'`'*) echo backtick;;
esac

All of those should work IMHO, but $b might not contain a backslash when you expect it to. Consider:

vnix$ echo 'foo\bar'
foo\bar
vnix$ echo 'foo\bar' | while read b; do
>   set | grep b=
> done
b=foobar
bash205b='3.2.25(1)-release'

See? The backslash is parsed already by read. My bash has an option read -r to disable this but I don't know if that's portable.

Yeah, I agree. I will do what I can first.

If anyone has a better solution, please let me know. I am still desperate for a solution. I regret for agreeing to use Bourne shell to develop this simple application. :frowning: