Arithmetic Expansion
Shell 에서 명령문 작성시 산술연산이 필요할 때 사용하는 표현식입니다.
사용 방식은 (( ))
특수명령 과 동일하지만
이것은 괄호 앞에 $
문자가 있어서 명령으로는 사용할 수 없고
$( )
명령치환과 같이 연산 결과를 변수에 대입하거나 명령의 인수 부분에서 사용됩니다.
명령치환 과는 달리 subshell 이 아닌 현재 shell 에서 실행되므로 현재 shell 의 변수값을
변경할 수 있습니다.
식을 작성할 때는 매개변수 확장과 명령 치환도 모두 사용할 수 있습니다.
같은 기능을 하는
$[ ]
표현식은 deprecated 라고 하니 사용하지 말아야겠습니다.
산술연산을 하는 외부 명령으로는expr
,bc
등이 있습니다.
# 연산 결과값을 변수에 대입시킬 수 있다.
$ AA=$(( 1 + 2 )); echo $AA
3
$ CC=$(( BB = AA += 2 ))
$ echo $AA $BB $CC
5 5 5
# 명령의 인수 부분에서 사용될 수 있다.
$ expr 100 + $(( AA + 2 ))
107
# 식 내에서는 매개변수 확장, 명령 치환도 모두 사용할 수 있다.
$ B=1000
$ echo $(( B += $(date | awk '{print $4}') ))
3020
변수 이름 활용
$(( ... ))
안에서는 알파벳 문자가 나오면 그것은 변수 이름이라는 것 외에 다른 뜻이 없습니다.
따라서 기본적으로 변수 이름 앞에 $
문자를 붙이지 않아도 되는데
이것을 활용하면 다음과 같이 변수 이름을 만들어 사용할 수도 있습니다.
$ foo1=111 foo2=222
$ a=foo b=1 c=2
$ echo $(( foo1 + foo2 )) # 변수 이름 앞에 '$' 문자를 붙이지 않아도 된다.
333
$ echo $(( foo$b + foo$c )) # foo$b 는 foo1 이 되고, foo$c 는 foo2 가 된다.
333
$ echo $(( $a$b + $a$c )) # $a$b 는 foo1 이 되고, $a$c 는 foo2 가 된다.
333
abc_1=10
xyz_1=20
abc_2=30
xyz_2=40
abc_3=50
xyz_3=60
for i in {1..3}
do
echo xyz_$i + abc_$i == $(( xyz_$i + abc_$i ))
done
-------------------------
xyz_1 + abc_1 == 30
xyz_2 + abc_2 == 70
xyz_3 + abc_3 == 110
:
명령 활용하기
:
명령을 활용하면 (( ))
명령과 같이 단독으로 연산을 할 수 있습니다.
# 기본적으로 명령행 상에서 단독으로 사용할 수는 없다.
$ $(( 1 + 2 ))
3: command not found
$ AA=$(( 1 + 2 ))
# 하지만 ':' 명령을 활용하면 명령행 상에서 단독으로 연산을 할 수 있습니다.
$ : $(( AA += 1, BB = AA + 1 ))
$ echo $AA $BB
4 5
현재 shell 의 변수값을 변경할 수 있습니다.
$ AA=100
$ : $(( AA = 200 ))
$ echo $AA
200
8 진수, 16 진수 연산
숫자가 0
으로 시작하면 8 진수로, 0x
로 시작하면 16 진수로 인식합니다.
$ echo $(( 010 )) $(( 8#10 )) # 8 진수
8 8
$ echo $(( 0x10 )) $(( 16#10 )) # 16 진수
16 16
$ echo $(( 2#1010 )) # 2 진수
10
$ AA=032; echo $(( 10#$AA )) # 10 진수 ( 0 이 제거된다 )
32
$ echo $(( 010 + 0x10 + 10 + 2#10 )) # 8 진수 + 16 진수 + 10 진수 + 2 진수
36
8 진수나 16 진수로 변경하려면 printf 명령을 사용하면 됩니다.
$ printf %x $(( 0135 )) # 8 진수를 16 진수로 변경
5d
$ printf %o $(( 0x5d )) # 16 진수를 8 진수로 변경
135
$ printf %x $(( 2#11010101 )) # 2 진수를 16 진수로 변경
d5
Bitwise 연산
- >> (Right Shift) - Shift bits to right and adds '0's on left (Divide by 2 operation)
- << (Left Shift) - Shifts bits to left and add '0' on right (Multiply by 2 operation)
- & (Bitwise AND) - Performs AND operation on every bit and produces result
- | (Bitwise OR) - Performs OR operation on every bit and produces result
- ^ (Bitwise XOR) - Performs XOR operation on every bit and produces result
- ~ (Bitwise NOT) - Inverts all the bits
- <<= (Left Shift Assign)
- >>= (Right Shift Assign)
- &= (Bitwise AND Assign)
- |= (Bitwise OR Assign)
- ^= (Bitwise XOR Assign)
# Right Shift
echo $(( 15 >> 1 )) # '1111' >> 1 = '0111' ( Ans : 7 )
echo $(( 15 >> 3 )) # '1111' >> 3 = '0001' ( Ans : 1 )
# Left Shift
echo $(( 15 << 1 )) # '1111' << 1 = '11110' ( Ans : 30 )
echo $(( 15 << 3 )) # '1111' << 3 = '1111000' ( Ans : 120 )
# Bitwise AND
echo $(( 15 & 1 )) # '1111' & '0001' = '0001' ( Ans : 1 )
echo $(( 15 & 3 )) # '1111' & '0011' = '0011' ( Ans : 3 )
# Bitwise OR
echo $(( 15 | 1 )) # '1111' | '0001' = '1111' ( Ans : 15 )
echo $(( 15 | 3 )) # '1111' | '0011' = '1111' ( Ans : 15 )
# Bitwise XOR
echo $(( 15 ^ 1 )) # '1111' ^ '0001' = '1110' ( Ans : 14 )
echo $(( 15 ^ 3 )) # '1111' ^ '0011' = '1100' ( Ans : 12 )
# Bitwise NOT
echo $(( ~1 )) # ~ '0000 0001' = '1111 1110' ( Ans : -2 )
echo $(( ~3 )) # ~ '0000 0011' = '1111 1100' ( Ans : -4 )
echo $(( ~15 )) # ~ '0000 1111' = '1111 0000' ( Ans : -16 )
연산자 우선순위는 C 언어와 같습니다.
~
> shift 연산
> <, <=, >, >=
> ==, !=
> &
> ^
> |
> &&
> ||
# shift 연산이 우선순위가 높기 때문에 이것은 2 | ( 4 >> 1 ) 와 같다.
$ echo $(( 2 | 4 >> 1 ))
2
$ echo $(( ( 2 | 4 ) >> 1 ))
3
# &, ^, | 연산은 ==, != 등호 보다도 우선순위가 낮다.
# 따라서 다음 식은 2 | ( 4 == 3 ) 와 같다.
$ echo $(( 2 | 4 == 3 ))
2
$ echo $(( ( 2 | 4 ) == 3 ))
0
거듭제곱 연산
거듭제곱은 연산자로 **
를 사용합니다 ( ^
는 XOR 연산자로 사용되므로 ).
한가지 사용시 주의할 점은 연산 방식이 요즘 사용되는 프로그램들과 다릅니다.
이것은 과거의 유물로 수정되지 못하고 남아있는 기능인데
음수를 거듭제곱할 경우 보통 -
를 제외하고 먼저 거듭제곱을 한후에
결과에 -
가 붙는데 반해서 shell 에서는 음수 자체를 거듭제곱합니다.
이것은 ksh, zsh, bc 에서도 모두 동일합니다.
$ echo $(( -2 ** 2 )) # (-2) 를 2 제곱 한것과 같다.
4 # (ksh, zsh, bc 모두 동일)
$ echo $(( -(2 ** 2) ))
-4
$ bc -l <<< '-2 ^ 2' # bc 도 오래된 명령이라 연산방식이 같다.
4
-------------------------------
$ awk 'BEGIN { print -2 ^ 2 }' # 그외 계산기나 요즘 사용되는 프로그램의 경우는
-4 # 2 에 2 제곱을 한후에 결과에 "-" 가 붙습니다.
# ("^" 연산자가 "-" 보다 우선순위가 높으므로)
$ perl -E 'say -2 ** 2'
-4
$ python3 -c 'print (-2 ** 2)'
-4
$ qalc '-2 ^ 2'
−(2^2) = −4
소프트웨어는 이게 문제 입니다. 연산을 하는데 기본적인 기능을 처음에
잘못 정의하면 나중에 고칠수가 없습니다 ( 기존에 작성된 프로그램이
모두 사용할 수 없게 되므로 ).
shell 방식이 문제가 되는것은 다음과 같이 -
연산자의 개수가 변경이 돼도 부호가
바뀌질 않습니다.
$ echo $(( 10 - 3 ** 2 )) # 10 - ( 3 ** 2 ) = 10 - 9 = 1
1
$ echo $(( 10 - - 3 ** 2 )) # 10 - ( - 3 ** 2 ) = 10 - 9 = 1
1
$ echo $(( 10 - - - 3 ** 2 )) # - - 는 + 이므로
1 # 10 - ( - - 3 ** 2 ) = 10 - ( 3 ** 2 ) = 10 - 9 = 1
$ python3 -c 'print(10 - 3 ** 2)' # 10 - ( 3 ** 2 ) = 10 - 9 = 1
1
$ python3 -c 'print(10 - - 3 ** 2)' # - - 는 + 이므로
19 # 10 + ( 3 ** 2 ) = 10 + 9 = 19
$ python3 -c 'print(10 - - - 3 ** 2)' # - - - 는 - 이므로
1 # 10 - ( 3 ** 2 ) = 10 - 9 = 1
Quiz
입력 값을 2 진수로 변환해서 출력하는 명령을 만드는 것입니다. 명령은 파일명을 인수로 받거나 파이프로 연결해 사용할 수 있어야 합니다.
같은 기능을 하는 명령으로
$ xxd -b file
가 있습니다.
$ binary data.bin $ echo 한글 | binary
. . . . . .
$ binary data.bin 4 $ cat data.bin | binary 4 # 4 는 컬럼 개수
. . . . . .
---------------------------------------------------------------
binary ()
{
# stdin 이 터미널인데 전달된 인수가 없거나 "-h" , "--help" 이면 usage 를 출력
if [[ -t 0 && ( ${#@} == 0 || $1 == "-h" || $1 == "--help" ) ]]; then
echo -e "\nUsage: $FUNCNAME file [cols]\n" >&2
return 1
fi
local file cols command # stdin 이 터미널이 아니면 cols 변수값만 설정
test -t 0 && { file=\"$1\" cols=$2 ;} || cols=$1
read -rd "" command <<@
perl -lpe '\$_=unpack "B*"' $file | fold -w8
@
if test $((cols)) -gt 0; then
local dash
printf -v dash "%${cols}s"
command+="| paste -d ' ' ${dash// / -}"
else
command+="| paste -s -d ' '"
fi
eval "$command"
}
1. cols 변수는 옵션인데 만약에 if test "$cols" -gt 0; then ... 와같이 작성한다면 cols 변수값이
설정되지 않았을 경우 test 명령문에서 "integer expression expected" 오류가 발생합니다.
이때 $(( )) 표현식을 사용하면 설정되지 않은 변수에 대해 0 이 반환되므로 오류를 방지할수 있습니다.
2. perl 명령문에서 사용된 $file 변수는 원래 "$file" 와같이 quotes 을 사용해야 되는데요.
(파일 이름에 공백이 있을수 있으므로). 하지만 quotes 을 사용하게 되면 stdin 이 터미널이
아닐경우 $file 변수값이 존재하지 않게 되므로 "" 인수 오류가 발생합니다.
따라서 이때는 quotes 처리를 $file 변수값을 설정하는 곳에서 file=\"$1\" 와같이 해줍니다.
그러면 eval 에의해 명령문이 실행될때 최종 명령문에는 quotes 이 붙게 됩니다.
3. perl -lpe '$_=unpack "B*"' 명령문에서 $_ 변수는 shell 에서 사용되므로 escape 해야합니다.