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