Created
February 1, 2016 03:48
-
-
Save izabera/c87659f14818a0dd804f to your computer and use it in GitHub Desktop.
parse a block of code correctly even with heredocs or compound commands or pipelines or ...
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/bin/bash | |
shopt -s expand_aliases extglob | |
__eval () { | |
unset __code __tmp __commands | |
while (( $# )); do | |
if [[ $1 && $1 = *[![:space:]]* ]]; then | |
printf "==== command ====\n%s\n=================\n" "$1" | |
time eval "$1" | |
fi | |
shift | |
done | |
} | |
__analyzetimer () { | |
local __code __commands=('') __tmp | |
IFS= read -rd '' -u"$__timer" __code | |
exec {__timer}>&- | |
# stupid way to check if a string is valid syntax | |
if [[ $(bash -O extglob -nc -- "$__code" 2>&1) ]]; then | |
# check syntax with bash -n, then check if bash printed an error message | |
# exit status is not enough, e.g.: bash -nc ': << x' returns 0 | |
echo "that doesn't look like valid syntax" >&2 | |
return 1 | |
fi | |
# sorta-parser | |
# this could probably be cleaner with =~ | |
# but this way BASH_REMATCH is kept intact | |
while [[ ${#__code} -ne 0 && ${__code} = *[$'\n&;#']* ]]; do | |
# get text up to (and including) next \n or & or ; | |
__tmp=${__code%%[$'\n&;#']*} | |
__tmp=${__code::${#__tmp}+1} | |
__code=${__code:${#__tmp}} | |
# add it to array | |
__commands[-1]+=$__tmp | |
# if this isn't valid syntax, we're inside one of: | |
# - comsub | |
# - procsub | |
# - compound command | |
# - some kind of quoting | |
# so we don't start a new command | |
if [[ ! $(bash -O extglob -nc -- "$__code" 2>&1) ]]; then | |
# check if the last character was escaped, in which case don't start a new one | |
__tmp=-2 | |
# if this is a backslash, maybe it also was escaped | |
while [[ ${__commands[-1]:__tmp--:1} = '\' ]]; do | |
[[ ${__commands[-1]:__tmp--:1} = '\' ]] || break | |
done | |
# now if __tmp is *even*, we found an *odd* number of \ so it was escaped | |
if (( __tmp % 2 )); then | |
__tmp=0 | |
# handle special cases | |
case ${__commands[-1]: -1} in | |
'#') # remove everything up to end of line | |
__commands[-1]=${__commands[-1]::-1} | |
__code=${__code#*$'\n'} ;; | |
'&') # if it's part of &&, don't start a new command | |
[[ ${__code::1} = '&' ]] && __tmp=1 ;; | |
esac | |
# if everything is ok, we're done! | |
if [[ __tmp -eq 0 && ! $(bash -O extglob -nc -- "${__commands[-1]}" 2>&1) ]]; then | |
# clean it up and start a new command | |
__tmp=${__commands[-1]##+([$' \t\n'])} | |
__commands[-1]=${__tmp%%+([$' \t\n'])} | |
__commands+=('') | |
fi | |
fi | |
fi | |
done | |
(( ${#__code} )) && commands+=("$__code") | |
__eval "${__commands[@]}" | |
} | |
alias $'timer{=__analyzetimer {__timer}<< \\}timer\n' | |
timer{ | |
true && | |
echo 'hello $( world )' ";" \\\; /!(bin|usr) \ | |
foo bar || echo false \\ \\\\; | |
if false; then | |
echo it was true | |
else | |
case foo in | |
foo) echo 'it was false and it matched foo | |
newline';; | |
bar) echo something went really wrong ;; | |
esac | |
fi | |
echo "background: $BASHPID" & wait # you can also use comments | |
echo "foreground: $BASHPID" | |
while read -r; do | |
echo "${REPLY^^}!!!!" | |
done << x | |
this is $(echo sparta) | |
x | |
echo x | cat -n | |
echo y |& cat -n | |
}timer |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment