Dash

Bash 가 interactive shell 이라면 sh 은 script 용 shell 이라고 할 수 있을 정도로 sh 은 bash 에 비해 많은 기능이 빠져있고 script 작성에 필요한 최소한의 기능만을 가지고 있습니다. sh 은 POSIX 에서 규정한 일종의 specification 으로 이를 구현한 구현체 중의 하나가 dash (debian almquist shell) 입니다. sh 은 POSIX 시스템에 기본적으로 포함되므로 각기 다른 shell 환경을 가지는 여러 종류의 OS 에서 호환성의 문제없이 실행될 수 있습니다. 그러므로 배포용 스크립트를 작성할 때는 주로 sh 을 사용합니다.

sh 은 bash 에 비해 실행파일의 크기도 작고 실행 속도도 빨라서 빠른 start-up time 을 요구하는 boot script 을 작성할 때나 특정 프로그램에서 shell 을 이용해 외부 명령을 실행시킬 때 bash 보다는 sh 을 사용합니다.

http://unix.stackexchange.com/a/148061/117612 에서 bash 와 비교한 것을 볼 수 있습니다.

sh 로 작성한 스크립트가 실행될 때는 bash 에서 export 한 변수는 사용할 수 있지만 함수는 사용할 수 없습니다. ( 따라서 원본 명령 실행을 위해 command 명령을 사용할 필요가 없습니다 ). sh 은 script 를 작성하는데 필요한 기본적인 기능만 제공합니다. 그러므로 [[ ]], (( )), let 과 같은 bash 에서만 제공하는 특수 명령은 사용할 수 없습니다. ( 보통 bashism 이라고 합니다 )

echo

sh 에서 echo 명령은 bash 에서 echo -e 와 같습니다.
그러므로 기본적으로 escape 문자가 처리됩니다.

sh$ echo -n ' \n\t' | od -a
0000000  sp  nl  ht

escape 문자가 처리되지 않게하려면 printf "%s\n" ... 를 사용하면 됩니다.
자세한 내용은 sh quotes 이해하기 를 참조하세요

8 진수 형식의 사용

sh 에서는 echo 와 printf 문에서 16 진수 형식을 사용할 수 없습니다.
자세한 사항은 escape sequences 메뉴를 참고하세요

# sh 은 8 진수 형식만 사용가능
sh$ echo '\101\102\103'
ABC
sh$ echo '\0101\0102\0103'
ABC
sh$ echo '\x41\x42\x43'             # \x<HH> 16 진수 형식 안됨
\x41\x42\x43

test 명령에서 스트링 equal 연산자

sh 의 builtin 명령인 test 의 경우 bash 와달리 스트링 equal 연산자로 == 를 사용할 수 없습니다.
= 를 사용해야 합니다. ( != 은 동일 )

bash$ [ "foo" == "foo" ]; echo $?         # bash 에서는 사용가능
0

$ /usr/bin/test "foo" == "foo"; echo $?   # /usr/bin/test 외부 명령도 사용가능
0

sh$ [ "foo" == "foo" ]; echo $?           # sh 에서는 오류
sh: 1: test: foo: unexpected operator
2

sh$ [ "foo" = "foo" ]; echo $?            # sh 에서는 '=' 연산자를 사용해야 한다.
0

IFS 값의 설정

sh 에는 $' ' 이 없습니다. 그러므로 IFS 값을 변경하려면 다음과 같이 합니다.

# IFS 값을 newline 으로 설정
sh$ IFS='
'

# IFS 값을 기본값으로 설정
# $( ) 을 이용해 변수에 값을 대입할때는 마지막 newline 이 제거되기 때문에 \t 을 \n 뒤에 두어야 합니다
sh$ IFS=$(echo " \n\t")
# 또는
sh$ unset IFS

$(( . . . ))

산술 확장은 sh 에서 사용 가능하지만 다음 연산자들은 사용할 수 없습니다.

++ , -- 연산자

sh$ : $(( i++ ))
sh: 2: arithmetic expression: expecting primary: " i++ "  # 오류발생

# 다음과 같이 수정해 사용합니다.

i=$((i+1))   또는   i=$((i-1))
: $((i+=1))  또는   : $((i-=1))

, 연산자

sh$ : $(( i+=1, i+=1 ))  
sh: 21: arithmetic expression: expecting EOF: " i+=1 , i+=1 "  # 오류발생

** 연산자

sh$ echo $(( 2**3 ))
sh: 363: arithmetic expression: expecting primary: " 2**3 "  # 오류발생

Parameter expansion

sh 에서 다음 기능은 사용할 수 없습니다.

Substring expansion : ${parameter:offset:length} ...

Search and replace : ${parameter/pattern/string} ...

Indirection : ${!parameter}

Case modification : ${parameter^^}, ${parameter,,} ...

Redirections

1>&3- 는 두 단계로 나누어서 합니다. 1>&3 3>&-

command &> filecommand > file 2>&1 로 변경합니다.

command1 |& command2command1 2>&1 | command2 로 변경합니다.

read

-r 옵션만 사용가능 합니다.

FD 번호

sh 에서는 redirection 이나 exec 에서 사용할 수 있는 FD 번호가 single-digit file descriptors 로 제한됩니다. ( 0 ~ 9 번까지 ) 그리고 named file descriptor 는 사용할 수 없습니다.

command history 기능

sh 에서는 command history 기능을 제공하지 않습니다. 그러므로 프롬프트 상에서 history 확장을 금지하기 위해 ! 문자를 escape 할 필요가 없습니다.

export

sh 에서는 변수만 export 할 수 있고 function 은 export 할 수 없습니다.
따라서 bash 에서 export -f 한 함수는 sh 에서 사용할 수 없습니다.

명령에 선행하는 대입 연산

read 명령에서는 bash 와 동일하게 동작하나 그외 eval 이나 shell 함수를 사용할 때와 같이 프로세스가 생성되지 않는 경우는 변수에 값이 대입돼버리고 이전 값으로 복귀가 안됩니다.

# read 명령에서는 bash 와 동일하게 동작
sh$ echo -n "$IFS" | od -a
0000000  sp  ht  nl
sh$ IFS=: read v1 v2 v3      # [enter]
1:2:3                        # 입력
sh$ echo $v1 $v2 $v3
1 2 3
sh$ echo -n "$IFS" | od -a   # 원래 값으로 복귀
0000000  sp  ht  nl

# eval 을 사용할 경우
sh$ a=1 b=2 c=3
sh$ a=4 b=5 c=6 eval 'echo $a $b $c'
4 5 6
sh$ echo $a $b $c            # 원래 값으로 복귀가 안되고 변수에 값이 대입됨     
4 5 6

# 스크립트 파일을 실행하는 것처럼 프로세스가 생성되는 경우는 정상작동합니다.
sh$ echo $a $b $c      
1 2 3
sh$ a=4 b=5 c=6 sh -c 'echo $a $b $c'
4 5 6
sh$ echo $a $b $c
1 2 3

Bashism

다음에 이어지는 내용은 bash 에서만 사용할 수 있는 기능으로 sh 에서는 사용할 수 없습니다.

test 명령에서 '==' 의 사용

[ "$var1" == "$var2" ]   # bashism 으로 사용할 수 없음

[ "$var1" = "$var2" ]    # '=' 로 수정

+=

+= 대입 메타문자는 사용할 수 없습니다. 대신에 다음과 같은 식으로 사용하면 됩니다.

### bash ###
bash$ AA=hello
bash$ AA+=" World"
bash$ echo "$AA"
hello World

### sh ###
sh$ AA=hello
sh$ AA="$AA World"    # 또는 "$AA"" World"
sh$ echo "$AA"
hello World

$'   ' , $"   "

bashism 으로 사용할 수 없습니다.

Array

Array 는 POSIX 에 정의되어있지 않으므로 indexed array, associative array 모두 사용할 수 없고 AA=() 표현식도 사용할 수 없습니다. 대신에 다음과 같이 positional parameters 를 활용할 수 있습니다.

#!/bin/sh

line="11:22:33:44:55"

set -f; IFS=:        # globbing 을 disable
set -- $line         # IFS 값에 따라 필드를 분리하여 positional parameters 에 할당
set +f; unset IFS

echo number of fields = $#
echo field 1 = "$1"
echo field 2 = "$2"

shift 3
echo \$@ = "$@"

############# output #############

number of fields = 5
field 1 = 11
field 2 = 22
$@ = 44 55

$* 와 $@

sh 에서는 $@ 값을 변수에 대입할 경우 $* 와 동일하게 IFS 변수의 첫번째 문자가 인수의 구분자로 사용됩니다.

# bash 의 경우                                  # sh 의 경우
$ set -- 11 22 33 44 55                        $ set -- 11 22 33 44 55
$ IFS=XYZ                                      $ IFS=XYZ

$ foo=$*                                       $ foo=$*
$ echo "$foo" , $foo                           $ echo "$foo" , $foo
11X22X33X44X55 , 11 22 33 44 55                11X22X33X44X55 , 11 22 33 44 55

$ bar=$@                                       $ bar=$@
$ echo "$bar" , $bar                           $ echo "$bar" , $bar
11 22 33 44 55 , 11 22 33 44 55                11X22X33X44X55 , 11 22 33 44 55

[[ . . . ]] , (( . . . ))

for ((i=0; i<3; i++))...
bashism 으로 모두 사용할 수 없습니다.

Brace expansion

사용할 수 없습니다.

Process substitution

사용할 수 없습니다.

<<<

here string 은 사용할 수 없습니다.

function

function myfunc() { ... ;}  # function 키워드는 사용할 수 없음

myfunc() { ... ;}           # 수정

source

source ./subscript.sh       # source 명령은 사용할 수 없음

. ./subscript.sh            # '.' 명령으로 수정

declare

declare 는 사용할 수 없지만 함수 내에서 local 은 사용할 수 있습니다.

select, disown

사용할 수 없습니다.

shopt

shopt 명령을 이용한 shell 옵션 설정은 bash 전용으로 sh 에서는 사용할 수 없습니다.

$LINENO, $PIPESTATUS

사용할 수 없습니다.

$RANDOM

# $RANDOM 변수는 사용할 수 없으므로 다음과 같은 방법을 이용합니다.

$ random=$(( `od -An -N1 -tu1 /dev/urandom` % 100 ))

$ random=$(shuf -n1 -i 1-99)

$ random=$(cat <(seq 10 20) <(seq 80 90) | shuf -n1)  # 10~20 80~90 사이 수

$ random=$(shuf -n1 -e a b c d e)

$ shuf -i 100-200 | awk '{ printf "%4d,", $1 } NR % 10 == 0 {print ""}'
 189, 135, 110, 119, 149, 137, 194, 105, 195, 193,
 102, 192, 122, 157, 183, 198, 141, 120, 108, 175,
 127, 124, 138, 173, 174, 123, 176, 190, 134, 104,
 . . . 
$ awk 'BEGIN { srand(); for (i=0; i<100; i++) print int(rand()*100) }' |
xargs -n20 | column -t
34  63  65  37  14  97  62  55  52  88  42  50  72  40  60  24  52  18  95  65
77  72  71  35  54  57  31  24  81  66  78  18  73  5   69  24  49  83  11  4
70  13  1   50  65  88  45  10  16  56  19  78  66  62  60  3   73  87  24  44
56  83  16  51  15  80  17  32  29  92  47  0   88  31  81  53  94  55  79  31
50  46  12  89  61  33  68  22  80  12  36  17  84  62  80  54  60  29  54  3

bash 스크립트를 sh 스크립트로 변경할 때 고려해야 될 것

  • echo ...printf "%s\n" ... 로 변경
  • echo -n ...printf "%s" ... 로 변경
  • echo -e ...echo ... 로 변경

sh 스크립트를 bash 스크립트로 변경할 때 고려해야 될 것

sh 에서 echo 명령은 기본적으로 bash 에서 echo -e 와 같으므로

  • echo ...echo -e ... 로 변경

sh quotes 이해하기

sh 에서 echo 명령은 bash 에서 echo -e 를 사용한 것과 동일한 결과를 갖습니다. 따라서 quotes 을 해석할 때는 먼저 bash 에서 동일한 명령을 실행해 보고 나온 결과에 대해서 escape sequence 를 적용하면 됩니다.

sh$ echo "11\\t22\\t33"         # sh 에서의 quotes 처리를 해석하려면 
11      22      33

bash$ echo "11\\t22\\t33"       # bash 에서 동일한 명령을 실행해서 결과를 출력해보고
11\t22\t33                      # 출력 결과에 대해서 escape sequence 를 적용하면 된다.
# < No quotes >              < Double quotes >              < Single quotes >

$ echo 11\t22\t33            $ echo "11\t22\t33"            $ echo '11\t22\t33'
11t22t33                     11     22     33               11     22     33

$ echo 11\\t22\\t33          $ echo "11\\t22\\t33"          $ echo '11\\t22\\t33'
11     22     33             11     22     33               11\t22\t33

$ echo 11\\\t22\\\t33        $ echo "11\\\t22\\\t33"        $ echo '11\\\t22\\\t33'
11     22     33             11\t22\t33                     11\    22\    33

$ echo 11\\\\t22\\\\t33      $ echo "11\\\\t22\\\\t33"      $ echo '11\\\\t22\\\\t33'
11\t22\t33                   11\t22\t33                     11\\t22\\t33

$ echo 11\\\\\t22\\\\\t33    $ echo "11\\\\\t22\\\\\t33"    $ echo '11\\\\\t22\\\\\t33'
11\t22\t33                   11\    22\    33               11\\   22\\   33