Precedence
&& , ||
Shell 에서 주의할 점 중의 하나가 &&
, ||
메타문자 우선순위입니다.
보통 프로그래밍 언어에서는 &&
가 ||
보다 우선순위가 높지만 shell 에서는
앞에서부터 차례로 명령이 실행되므로 우선순위를 같게 취급합니다.
따라서 다음과 같은 구문이 있을 경우
a || b && c
a 가 true
일 때 구문해석과 실행은 다음과 같습니다.
언어 | 해석 | 실행 |
---|---|---|
c/c++, java | ( a ) || ( b && c ) | a |
shell | ( a || b ) && c | a, c |
결과적으로 프로그래밍 언어 에서는 a 가 실행이 된 후에 구문이 종료되나 shell 에서는 ( a || b ) 결과가 true 이므로 이후에 ( true && c ) 식이 실행되어 c 도 함께 실행되게 됩니다.
# $(( )) 에서는 프로그래밍 언어와 우선순위가 같으므로 a = 100 만 실행된다.
$ : $(( ( a = 100 ) || ( a = 200 ) && ( a = 300 ) ))
$ echo $a
100
# shell 의 경우는 먼저 a=100 가 실행되고 뒤이어 a=300 도 실행된다.
$ { echo 100; a=100 ;} || { echo 200; a=200 ;} && { echo 300; a=300 ;}
100
300
$ echo $a
300
우선순위 조절
Shell 에서 메타문자 우선순위 조절은 { ;}
, ( )
두가지 방법을 이용할 수 있습니다.
( )
는 subshell 이 생성돼야 하므로 가능하면 { ;}
를 사용하는게 좋겠습니다.
$ true || true && false ; echo $?
1
# { ;} 를 사용
$ true || { true && false ;} ; echo $?
0
# ( ) 를 사용
$ true || ( true && false ) ; echo $?
0
파이프와 &&, ||
파이프는 그룹으로 하나의 명령처럼 실행되므로 &&
, ||
보다 우선순위가 높습니다.
따라서 다음 명령문은 좌에서 우로 순서 대로 실행됩니다.
# 먼저 sleep 11 가 실행을 완료하면 이후에 sleep 12 와 sleep 13 이 동시에 실행됩니다.
$ sleep 11 && sleep 12 | sleep 13
# sleep 11 와 sleep 12 가 동시에 실행되고 완료되면 마지막으로 sleep 13 이 실행됩니다.
$ sleep 11 | sleep 12 && sleep 13
파이프 보다는 redirection 이 우선순위가 높다.
파이프 보다 redirection 이 우선순위가 높으므로
redirection 은 &&
, ||
보다 우선순위가 높겠죠.
$ echo hello | cat
hello
$ echo hello | cat <<< "world"
world
파이프가 & 보다 우선순위가 높다.
파이프는 그룹으로 하나의 명령처럼 실행되므로 파이프로 연결된 모든 명령들이 함께 background 로 실행됩니다.
$ command1 | command2 | command3 &
'!' shell keyword
!
logical NOT 키워드는 &&
, ||
보다 우선순위가 높습니다.
따라서 아래 첫 번째 경우는 !
키워드가 [ 1 -eq 2 ]
에만 적용되는 것입니다.
[ 1 -eq 2 ] || [ 3 -eq 3 ]
식 전체에 적용하려면 두 번째와 같이 작성해야 합니다.
$ if ! [ 1 -eq 2 ] || [ 3 -eq 3 ]; then echo YES ;fi
YES
$ if ! { [ 1 -eq 2 ] || [ 3 -eq 3 ] ;} then echo YES ;fi
$
# '!' 키워드는 다음과 같은 명령 위치에서 다양하게 사용할 수 있습니다.
$ if ! { ! [ ... ] || ! [ ... ] ;} then ...
파이프는 그룹으로 하나의 명령처럼 실행되므로 !
보다 우선순위가 높습니다.
( redirection 도 !
보다 우선순위가 높겠죠 )
$ if true | false; then echo YES; fi # true | false 는 결과가 false 이므로
$
$ if ! true | false; then echo YES; fi
YES
Redirection 우선순위
위에서 살펴본 바와 같이 redirection 은 명령문에서 우선순위가 제일 높습니다. redirection 끼리는 다음과 같은 순서로 실행됩니다.
- 좌에서 우 로 실행 됩니다.
>
메타문자에 의해 z1, z2 두 파일의 내용은 삭제되며 echo 명령의 출력은 z2 파일로 가게 됩니다.
$ echo foobar > z1 > z2
$ cat z1
$ cat z2
foobar
{ ;}
,( )
바깥에서 안쪽으로 실행됩니다.
z1, z2 두 파일의 내용은 삭제되며 echo 명령의 출력은 z1 파일로 가게 됩니다.
$ { echo foobar > z1 ;} > z2
$ cat z1
foobar
$ cat z2
Quiz
/boot 디렉토리에 위치한 vmlinuz-$(uname -r) 리눅스 커널 파일은 bootsect.o, setup.o, misc.o 와 piggy.o 로 구성된 bzImage( big zimage ) 파일로 piggy.o 안에 vmlinuz 커널 이미지가 압축되어 있습니다. 커널 이미지는 ELF 포멧으로 되어 있는데요. 어떻게 분리해 낼 수 있을까요?
zImage 는 압축되어있는 커널 이미지를 self-extracting 하여 실행시킬 수가 있어서 부팅이 가능합니다. vmlinuz 는 vmlinux 커널 이미지의 심볼을 strip 하고 gzip 한 것입니다.
$ sudo cp /boot/vmlinuz-4.13.0-37-generic .
$ sudo chmod +r vmlinuz-4.13.0-37-generic
$ file vmlinuz-4.13.0-37-generic
vmlinuz-4.13.0-37-generic: Linux kernel x86 boot executable bzImage, version
4.13.0-37-generic (buildd@lcy01-amd64-026) #42-Ubuntu SMP Wed Mar 7 14:13:23
UTC 2018, RO-rootFS, swap_dev 0x7, Normal VGA
# \037\213\010 은 gzip 으로 압축된 파일의 처음 3 bytes 값입니다.
# sh 에서는 $'\037\213\010' 를 "$(echo "\037\213\010")" 로 바꿔주면 됩니다.
$ N=$(grep -abo -m1 $'\037\213\010' vmlinuz-4.13.0-37-generic |
awk -F: '{print $1+1}') &&
tail -c +$N vmlinuz-4.13.0-37-generic |
gzip -d > vmlinuz
gzip: stdin: decompression OK, trailing garbage ignored
$ file vmlinuz
vmlinuz: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked,
BuildID[sha1]=6bdee301cd0ea1b997183c1e367b640cf42aed7d, stripped
$ readelf -h vmlinuz
ELF Header:
Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
Class: ELF64
Data: 2's complement, little endian
Version: 1 (current)
OS/ABI: UNIX - System V
ABI Version: 0
Type: EXEC (Executable file)
Machine: Advanced Micro Devices X86-64
Version: 0x1
Entry point address: 0x1000000
Start of program headers: 64 (bytes into file)
Start of section headers: 28160816 (bytes into file)
Flags: 0x0
Size of this header: 64 (bytes)
Size of program headers: 56 (bytes)
Number of program headers: 5
Size of section headers: 64 (bytes)
Number of section headers: 47
Section header string table index: 46