맺음말
쉘 스크립트는 프로그래밍 언어가 아닙니다. 시스템에 존재하는 명령들을 조합해서 실행하는데 도움을 주는 역할을 합니다. 반복문을 이용해 외부 명령을 실행하면 속도가 많이 느려질 수 있습니다.
sh
은bash
에 비해 기능도 적지만 아직도 스크립트를 작성하는데 주로 사용됩니다. 이것은sh
이 가진 기능 만으로도 충분히 쉘 스크립트 목적을 달성할 수 있기 때문입니다. 또한 특정 명령에서 쉘을 이용해 외부 명령을 실행할 때bash
보다는sh
을 사용하므로sh
의 사용법도 함께 알아두어야겠습니다./bin
,/usr/bin
,/sbin
,/usr/sbin
디렉토리에 있는 명령들을 알고 사용할 수 있어야겠습니다.
다음은 shell 스크립트를 활용한 산술연산 파서 입니다.
기본적으로 연산에 $(( ))
표현식을 사용하므로 소수 사용이나
지수를 음수로하는 거듭제곱 연산은 안됩니다.
그리고 거듭제곱 연산은 shell 방식을 따릅니다.
$ ./parse.sh '1 + (((((2 * 3) + 1) / 2) + 1) ** 2 * 2)'
33
$ echo $(( 1 + (((((2 * 3) + 1) / 2) + 1) ** 2 * 2) )) # 맞는지 테스트
33
$ python3 -c 'print( 1 + (((((2 * 3) + 1) / 2) + 1) ** 2 * 2) )'
41.5
$ ./parse.sh '-5 ** 2' $ ./parse.sh '1 + 2 * 3 ** (2 % 5) / 2'
25 10
$ python3 -c 'print(-5 ** 2)' $ python3 -c 'print(1 + 2 * 3 ** (2 % 5) / 2)'
-25 10.0
$ cat parse.sh
#!/bin/bash
str=$1 toks=() stack=()
len=0 cnt=0 paren=0
############ tokenizer ###########
for (( i = 0; i < ${#str}; i++ )); do
tok=${str:i:1}
case $tok in
[0-9])
while [[ ${str:i+1:1} == [0-9] ]]; do
tok+=${str: ++i:1}
done
toks+=( "$tok" )
;;
+ | - | '*' | / | % | '(' | ')')
if [[ $tok == '*' && ${str:i+1:1} == '*' ]]; then
tok="**"; let i++
fi
toks+=( "$tok" )
;;
[[:blank:]])
;;
*)
echo -e "\nError: tokenizer: \"$tok\"\n" >&2
exit 1
esac
done
len=${#toks[@]}
############## stack #############
push()
{
eval "$1[\${#$1[@]}]=$2"
}
pop()
{
eval local len="\${#$1[@]}"
if (( len > 0 )); then
eval "$2=\${$1[len - 1]}"
unset -v "$1[len - 1]"
eval "$1=( \"\${$1[@]}\" )"
else
error 0
fi
}
#####################################
error()
{
echo -en "\nError: "
for (( i = 0; i <= cnt + $1; i++ )); do
echo -n "${toks[i]} "
done
echo -n " <---"
[[ paren -gt 0 && ${FUNCNAME[1]} == main ]] && echo -n ' ")"'
echo
exit 1
} >&2
add()
{
local num1 num2 op next
pop stack num1
while [[ ${toks[cnt + 1]} == @(+|-) ]]; do
op=${toks[++cnt]}
let cnt++; pexp
pop stack num2
case ${toks[cnt + 1]} in
'*' | / | %) next="mul" ;;
'**') next="pow" ;;
esac
if [[ -n $next ]]; then
push stack $num2
$next
pop stack num2
fi
num1=$(( num1 $op num2 )) # 덧셈, 뺄셈 연산
done
push stack $num1
}
mul()
{
local num1 num2 op
pop stack num1
while [[ ${toks[cnt + 1]} == @('*'|/|%) ]]; do
op=${toks[++cnt]}
let cnt++; pexp
pop stack num2
if [[ ${toks[cnt + 1]} == '**' ]]; then
push stack $num2
pow
pop stack num2
fi
num1=$(( num1 $op num2 )) # 곱셈, 나눗셈, 나머지 연산
done
push stack $num1
}
pow()
{
local num1 num2 op
pop stack num1
op=${toks[++cnt]}
let cnt++; pexp
[[ ${toks[cnt + 1]} == '**' ]] && pow
pop stack num2
num1=$(( num1 $op num2 )) # 거듭제곱 연산
push stack $num1
}
pexp()
{
local minus num1
for (( ; cnt < len; cnt++ )); do
case ${toks[cnt]} in
*[0-9]*)
push stack "$minus${toks[cnt]}"
return ;;
+ | -)
if [[ -z $minus && ${toks[cnt]} == '-' ]] ||
[[ $minus == '-' && ${toks[cnt]} == '+' ]]; then
minus="-"
else
minus=""
fi ;;
'(')
let cnt++ paren++
parse
pop stack num1
push stack $(( $minus num1 ))
return ;;
*)
error 0
esac
done
}
parse()
{
pexp
while (( cnt + 1 < len )); do
case ${toks[cnt + 1]} in
+ | -) add ;;
'*' | / | %) mul ;;
'**') pow ;;
')')
let cnt++ paren--
(( paren < 0 )) && error 0
return ;;
*) error 1
esac
done
}
parse
(( paren > 0 )) && error 0
pop stack res
echo $res
awk 로 작성한 역폴란드 표기법 (RPN, Reverse Polish Notation) 파서
https://mug896.github.io/awk-script/operators.html#quiz
C 언어를 이용한 recursive descent parser 와 LR parser
https://github.com/mug896/arithmetic-parser