Skip to content

Instantly share code, notes, and snippets.

@d630
Last active June 4, 2022 00:44
Show Gist options
  • Save d630/d07faa982a1dc853a149946204e1b543 to your computer and use it in GitHub Desktop.
Save d630/d07faa982a1dc853a149946204e1b543 to your computer and use it in GitHub Desktop.
posix shell: understanding command search and execution
#!/bin/sh
get_path () {
command -v "$1";
};
has_operator () {
case "$1" in
(*";"*|*"&"*|*"&&"*|*"||"*|*";;"*|*"("*|*")"*)
command \return 1;;
(*"<"*|*">"*|*"<<"*|*">>"*|*"<&"*|*">&"*|*"<>"*|*"<<-"*|*">|"*)
command \return 2;;
esac;
};
has_slash () {
[ "${1##*/*}" = "$1" ];
};
is_builtin () {
[ "$1" = "built-in" ];
};
is_function () {
[ "$1" = "function" ];
};
is_reserved_word () {
case "$1" in
(if|then|else|elif|fi|do|done| \
case|esac|while|until|for| \
\{|\}|\!)
command \return 1;;
(\[\[|\]\]|function|select|?*:)
command \return 2;;
esac;
};
is_special_builtin () {
case "$1" in
(break|:|continue|.|eval|exec|exit|export|readonly|return|set|shift| \
times|trap|unset)
command \return 1;;
esac;
};
is_unspecified_utility () {
case "$1" in
( alloc|autoload|bind|bindkey|builtin|bye|caller|cap|chdir|clone| \
comparguments|compcall|compctl|compdescribe|compfiles|compgen| \
compgroups|complete|compquote|comptags|comptry|compvalues| \
declare|dirs|disable|disown|dosh|echotc|echoti|help|history| \
hist|let|local|login|logout|map|mapfile|popd|print|pushd| \
readarray|repeat|savehistory|source|shopt|stop|suspend|typeset|whence)
command \return 1;;
esac;
};
set_type () {
case "$(command \type "${1:-_}" 2>/dev/null)" in
(*\ function)
command \echo function;;
(*\ builtin)
command \echo built-in;;
(*\ keyword)
command \echo keyword;;
(*\ not\ found|"")
command \echo unkown;;
(*)
command \echo file;;
esac;
};
test_name ()
case "$1" in
(admin|ar|asa|at|awk|basename|batch|bc|c99|cal|cat|cflow|chgrp| \
chmod|chown|cksum|cmp|comm|compress|cp|crontab|csplit|ctags|cut| \
cxref|date|dd|delta|df|diff|dirname|du|echo|ed|env|ex|expand| \
expr|file|find|fold|fort77|fuser|gencat|get|getconf|grep|head| \
iconv|id|ipcrm|ipcs|join|lex|link|ln|locale|localedef|logger| \
logname|lp|ls|m4|mailx|make|man|mesg|mkdir|mkfifo|more|mv|nice| \
nl|nm|nohup|od|paste|patch|pathchk|pax|pr|printf|prs|ps|qalter| \
qdel|qhold|qmove|qmsg|qrerun|qrls|qselect|qsig|qstat|qsub|renice| \
rm| rmdel|rmdir|sact|sccs|sed|sh|sleep|sort|split|strings|strip| \
stty|tabs|tail|talk|tee|test|time|touch|tput|tr|tsort|tty|uname| \
uncompress|unexpand|unget|uniq|unlink|uucp|uudecode|uuencode|uustat| \
uux|val|vi|wc|what|who|write|xargs|yacc|zcat)
command \echo 1;;
(alias|bg|cd|command|false|fc|fg|getopts|hash|jobs|kill|newgrp|pwd|read| \
true|type|ulimit|umask|unalias|wait)
command \echo 2;;
esac;
iam () {
command \local -;
command \set -- ${1};
command \unalias "$1" 2>/dev/null;
command \local cmd=;
cmd="$1";
command \printf '%s\n' "Using token '${cmd}'";
command \printf '%s' "Testing if token matches operator ... ";
'has_operator' "$cmd";
case "$?" in
(0)
command \printf '%s\n' "False";;
(1)
command \printf '%s\n' "True";
command \printf '\t%s\n' "Token matches control operator";
command \printf '\t%s\n' "Skipping";
command \return 0;;
(2)
command \printf '%s\n' "True";
command \printf '\t%s\n' "Token matches redirection operator";
command \printf '\t%s\n' "Skipping";
command \return 0;;
esac;
command \printf '%s' "Testing if token is reserved word ... ";
'is_reserved_word' "$cmd";
case "$?" in
(0)
command \printf '%s\n' "False";;
(1)
command \printf '%s\n' "True";
command \printf '\t%s\n' "Skipping";
command \return 0;;
(2)
command \printf '%s\n' "True";
command \printf '\t%s\n' "Token is unspecific reserved word";
command \printf '\t%s\n' "Skipping";
command \return 0;;
esac;
command \printf '%s\n' "Starting command search and execution";
command \printf '%s' "Testing if name has slashes ... ";
'has_slash' "$cmd" || {
command \printf '%s\n' "True";
command \printf '\t%s' "Searching name using the PATH environment variable ... ";
command \local p=;
p="$('get_path' "$cmd")";
if
command \test -n "$p";
then
command \printf '%s\n' "Match";
case "$('test_name' "$(command \basename "$p")")" in
(1)
command \printf '\t\t%s\n' "Name is a POSIX standard utility";;
esac;
command \printf '\t\t%s\n' "Name points to file '${p}'";
command \printf '\t\t%s\n' "Executing file '${p}'";
command \return 0;
else
command \printf '%s\n' "Unmatch";
command \printf '\t%s\n' "Skipping command search and execution of '${cmd}'";
command \return 127;
fi;
};
command \printf '%s\n' "False";
command \printf '%s' "Testing if name is POSIX special built-in ... ";
if
'is_special_builtin' "$cmd";
then
command \printf '%s\n' "False";
else
command \printf '%s\n' "True";
command \printf '\t%s\n' "Executing special built-in '${cmd}'";
command \return 0;
fi;
command \printf '%s' "Testing if name is POSIX-unspecific utility ... ";
if
'is_unspecified_utility' "$cmd";
then
command \printf '%s\n' "False";
else
command \printf '%s\n' "True";
command \printf '\t%s\n' "Skipping command search and execution of '${cmd}'";
command \return 0;
fi;
command \local t=;
t="$('set_type' "$cmd")";
command \printf '%s' "Testing if name is definied function ... ";
if
'is_function' "$t";
then
command \printf '%s\n' "True";
command \printf '\t%s' "Testing if function is named as POSIX standard utility ... ";
case "$('test_name' "$cmd")" in
(1)
command \printf '%s\n' "True";
command \printf '\t\t%s\n' "Postponing execution of '${cmd}'";;
(2)
command \printf '%s\n' "True";
command \printf '\t\t%s\n' \
"Function is named as POSIX regular built-in" \
"Executing function '${cmd}'";
command \return 0;;
(*)
command \printf '%s\n' "False";
command \printf '\t%s\n' "Executing function '${cmd}'";
command \return 0;;
esac;
else
command \printf '%s\n' "False";
fi;
command \printf '%s' "Testing if name is built-in ... ";
if
'is_builtin' "$t";
then
command \printf '%s\n' "True";
command \printf '\t%s' "Testing if built-in is a POSIX regular built-in ... ";
case "$('test_name' "$cmd")" in
(1)
command \printf '%s\n' "False";
command \printf '\t%s\n' "Built-in is regular for this shell, but only a POSIX standard utility";
command \printf '\t%s\n' "Postponing execution of ${cmd}";;
(2)
command \printf '%s\n' "True";
command \printf '\t\t%s\n' \
"Executing POSIX regluar built-in '${cmd}'";
command \return 0;;
(*)
command \printf '%s\n' "False";
command \printf '\t%s\n' "Unknown POSIX non-standard built-in";
command \printf '\t%s\n' "Skipping command search and execution of '${cmd}'";
command \return 0;;
esac;
else
command \printf '%s\n' "False";
fi;
command \printf '%s' "Searching name using the PATH environment variable ... ";
command \local p=;
p="$('get_path' "$cmd")";
if
command \test -n "$p";
then
command \printf '%s\n' "Match";
case "$t" in
(built-in|function)
command \printf '\t%s\n' "Executing postponed ${t} '${cmd}'";
command \return 0;;
(file)
case "$('test_name' "$cmd")" in
(1)
command \printf '\t%s\n' "Name is a POSIX standard utility";
esac;
command \printf '\t%s\n' "Name points to file '${p}'";
command \printf '\t%s\n' "Executing file '${p}'";;
esac;
else
command \printf '%s\n' "Unmatch";
command \printf '%s\n' "Skipping command search and execution of '${cmd}'";
command \return 127;
fi;
};
# -- MAIN.
'iam' "${1:?}" 1>&2;
# vim: set ts=4 sw=4 tw=0 et :
@d630
Copy link
Author

d630 commented Nov 26, 2016

# no newline in token please

$ dash -c '. x.sh token'
$ dash x.sh token

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment