명령 의 구분

1. 외부 명령

/usr/bin/find 명령과 같이 시스템 디렉토리에 위치한 명령들입니다.
여기에는 ELF 바이너리 실행파일뿐만 아니라 shell, perl, python 스크립트와 같은 텍스트 실행파일도 많이 포함되어 있습니다. shell builtin 명령과 이름이 중복될 경우 직접 경로를 지정하여 실행할 수 있습니다.

# 텍스트 형태의 실행파일 개수
$ file /bin/* /usr/bin/* | grep -ic "text executable"
643

$ file /bin/* /usr/bin/* | grep -ic "shell script"
324

$ file /bin/* /usr/bin/* | grep -ic "perl script"
214

$ file /bin/* /usr/bin/* | grep -ic 'python[0-9.]* script'
97

$ file /bin/* /usr/bin/* | grep -i "shell script"
/bin/bzdiff:                                  POSIX shell script, ASCII text executable
/bin/bzexe:                                   POSIX shell script, ASCII text executable
/bin/bzgrep:                                  POSIX shell script, ASCII text executable
/bin/bzmore:                                  POSIX shell script, ASCII text executable
/bin/egrep:                                   POSIX shell script, ASCII text executable
/bin/fgrep:                                   POSIX shell script, ASCII text executable
/bin/gunzip:                                  POSIX shell script, ASCII text executable
...
...

2. shell builtins

Shell 에 builtin 돼서 제공되는 명령들로 실행을 위해 새로 프로세스를 만들지 않아도 되기 때문에 외부 명령에 비해 좀 더 효율적으로 실행할 수 있고 shell 내부 상태정보를 변경할 수 있습니다.

$ compgen -b | column

.         :         [         alias     bg        bind      break     
builtin   caller    cd        command   compgen   complete  compopt   
continue  declare   dirs      disown    echo      enable    eval      
exec      exit      export    false     fc        fg        getopts   
hash      help      history   jobs      kill      let       local     
logout    mapfile   popd      printf    pushd     pwd       read      
readarray readonly  return    set       shift     shopt     source    
suspend   test      times     trap      true      type      typeset   
ulimit    umask     unalias   unset     wait

echo, printf, test, [, true, false, kill, pwd 같은 명령들은 동일한 기능을 하는 외부 명령이 중복되어 존재하는데 이것은 shell 환경이 아닌 경우에도 사용할 수 있게 하기 위해서입니다.

# history 는 shell builtin 으로 env 명령으로 실행할 수가 없다.
$ env history
env: ‘history’: No such file or directory

# test, echo 같은 명령들은 shell builtin 이지만 외부 명령으로도 존재하므로 env 로 실행할 수 있다.
$ env test 1 == 2; echo $?
1

$ env echo 123
123

3. shell functions

사용자가 임의로 원하는 함수를 만들어 사용할 수 있습니다. 함수명은 외부 명령, builtin 명령과 동일하게 사용됩니다. declare -F 명령을 이용하면 현재 shell 에 설정돼있는 함수들을 볼 수 있습니다.

4. shell keywords

앞서 소개한 명령들은 사용시 command arg1 arg2 ... 형식을 취하고 실행전에 인수들의 확장과 치환작업을 거치나 키워드는 그에 앞서 구문을 해석할때 사용됩니다.

$ compgen -k | column

if        then      else      elif      fi        case      
esac      for       select    while     until     do        
done      in        function  time      {         }         
!         [[        ]]        coproc

5. aliases

앞서 소개한 명령들을 alias 하여 사용할수 있습니다. shell 키워드도 alias 해서 사용할 수 있을 정도로 우선순위가 제일 높습니다. non-interactive shell 인 script 파일에서는 기본적으로 disable 됩니다.

명령 이름이 중복되어 나타날때

명령 이름이 앞서 분류한 곳에 중복되어 나타나는 경우가 있습니다.
관련된 정보를 보려면 builtin 명령인 type 을 이용합니다.

$ type -a kill
kill is a shell builtin
kill is /bin/kill

$ type -a time
time is a shell keyword
time is /usr/bin/time

$ type -a [
[ is a shell builtin
[ is /usr/bin/[

명령을 찾는 우선순위는 alias, keyword, function, builtin, 외부 명령 순 입니다. kill 명령은 builtin 에도 있고 외부 명령에도 있는데 같은 이름의 function 을 새로 만든다면 function 이 실행되게 됩니다.

이렇게 중복되는 이름 문제를 해결하기 위해 shell 에서는 다음과 같은 명령들을 제공합니다.

  • command : 우선순위가 높은 alias, keyword, function 이름을 피해 외부명령, builtin 명령을 실행합니다.
    외부, builtin 명령의 기능을 확장하기 위해 동일한 이름의 함수를 만들어 사용할땐 함수안에서 command 명령 을 사용해야 원본 명령을 실행할 수 있습니다. 또한 배포를 위한 shell script 작성시 명령 앞에는 command 를 붙여야 원본 명령이 실행되는 것을 보장할 수 있습니다.

  • builtin : 우선순위가 높은 alias, function 이름을 피해 builtin 명령을 실행하기 위해 사용합니다.

  • enable : builtin 명령을 disable 하여 외부 명령을 실행하게 할 수 있습니다.

alias , keyword 의 escape

alias 나 keyword 는 특별한 기능을 하는 단어로 볼 수 있습니다. 우선순위 또한 일반 명령들 보다 높은데 다음과 같은 방법을 이용하면 해당 기능을 disable 할 수 있습니다.

  • alias, keyword 이름 앞에 \ 문자를 붙인다.

  • alias, keyword 이름을 quote 한다

time 은 keyword 이면서 /usr/bin/time 외부 명령이기도 한데 다음과 같이 하면 키워드 기능이 escape 되어 외부 명령을 실행할 수 있습니다.

$ \time
$ 'time'      # 또는 "time"

ls 명령이 alias 되어 있다면 다음 명령으로 alias 기능을 escape 하여 원본 명령을 실행할 수 있습니다.

$ \ls
$ 'ls'       # 또는 "ls"

function 이나 builtin 명령은 위 방법으로 escape 할 수 없습니다. builtin 명령과 중복되는 외부 명령이 있다면 전체 경로를 입력하여 실행하거나 enable 명령으로 builtin 명령을 disable 할 수 있습니다. function 의 경우는 command 명령 으로 외부명령을 실행할 수 있습니다.

명령들의 차이점

예제 .1

다음은 shell builtin [ 명령과 [[ 키워드의 사용상 차이점입니다.

$ AA="hello world"

$ if [ -n $AA ]; then
>       echo "$AA"
> fi
bash: [: hello: binary operator expected

----------------------------------------

$ if [[ -n $AA ]]; then
>       echo "$AA"
> fi
hello world

똑같은 문장인데 [ 명령 에서는 오류가 나고 [[ 키워드 에서는 정상적으로 실행이 됩니다. [ 는 앞서 살펴본 바와같이 builtin 명령으로 일반 명령들과 사용법이 같습니다. command arg1 arg2.. 이런식이죠. 그러므로 [ -n $AA ] 명령문이 변수확장이 되면 [ -n hello world ] 와 같게됩니다. 결과적으로 hello 를 binary 연산자로 -nworld 를 피연산자로 해석을 해서 hello 라는 연산자가 없다는 오류가 납니다. 반면에 [[ 키워드는 shell 에서 자체적으로 표현식을 해석하므로 $AA 값을 올바르게 인식합니다.

예제 .2

다음 예로 일반 명령과 키워드의 실제 사용되는 시점에 대해 알 수 있습니다.

$ a='['
$ $a -d /tmp ]
$ echo $?
0

$a 변수확장이 일어나고 [ -d /tmp ] 명령문이 실행되어 정상적인 결과값이 나왔습니다.

$ a='[['
$ $a -d /tmp ]]
bash: [[: command not found

$a 변수확장이 일어나고 [[ -d /tmp ]] 명령문이 실행되었으나 오류가 발생했습니다. 이 결과로 알 수 있는 것은 키워드 해석 작업은 변수확장 보다 우선해서 처리된다는 것입니다. 키워드 해석 작업이 이미 끝난 상태이므로 [[ 명령이 없다는 오류가 발생했습니다.

예제 .3

time 명령은 shell 키워드이고 /usr/bin/time 명령이기도 합니다. 먼저 키워드를 이용해 다음 문장을 실행해보면 오류 없이 정상적으로 실행이 됩니다.

$ time { sleep 1 ;}

real    0m1.003s
user    0m0.000s
sys     0m0.003s

이번에는 \ 를 이용해 키워드를 escape 해서 /usr/bin/time 명령으로 실행해보면 외부 명령으로는 구문 에러가 나는 것을 알 수 있습니다.

$ \time { sleep 1 ;}
bash: syntax error near unexpected token `}'

$ \time { sleep 1 }
time: cannot run {: No such file or directory
. . .
. . .

alias or function or script file ?

Shell 을 사용하다 보면 명령을 작성할 때 alias 로할지 function 으로 할지 아니면 외부 명령처럼 script 파일로 작성할지 애매한 경우가 있는데요. 차이점을 살펴보면 다음과 같습니다.

  • alias 는 shell 프롬프트 상에서만 사용할 수 있습니다. 그리고 함수에서처럼 인수를 줄 수가 없습니다.

function 을 정의할 때는 자동으로 alias 가 확장되어 정의됩니다.

  • sh 에서는 function 을 export 할 수 없지만 bash 에서는 가능합니다. 따라서 script 파일과 child shell process 가 bash 라면 export -f 한 함수를 사용할 수 있습니다.

vi 에디터에서 외부 명령을 실행할 때 alias 는 사용할 수 없지만 export -f 한 함수는 사용할 수 있습니다 (sh 이 아닌 bash 로 실행시키므로).

  • script 파일의 경우는 파일로 존재하므로 추가적인 설정 없이 외부 어떤 명령에서든지 실행시킬 수 있습니다. 예를 들어 env, sudo, xargs, find 명령의 -exec 액션으로 실행하려면 script 파일로 작성해야 합니다.

script 파일에서는 기본적으로 alias 가 disable 됩니다.

  • alias, function 은 현재 shell 에서 실행되므로 shell 의 환경 변수값을 바꾸거나 옵션 설정을 변경할 수 있습니다. 반면에 script 파일은 child process 가 생성되어 실행되므로 shell 환경을 변경할 수 없습니다.

  • alias 는 \ 로 간단하게 escape 할수 있는 반면에 function 의 경우는 command 명령을 사용해야 합니다.

그러므로 shell 프롬프트 상에서 주로 사용할 것이라면 alias, function 으로 작성하고 shell 외부 환경에서도 실행 가능해야 한다면 script 파일로 작성하면 됩니다.

Help 문서 보는법

shell builtin 명령의 help 문서를 볼 때는 help builtin 명령을 이용합니다. 외부 명령은 man 페이지, info 페이지를 이용하거나 command --help 형태로 조회할 수 있습니다.

명령 사용법에 나타나는 [...] 로된 부분은 옵션을 의미합니다. 그러니까 명령을 작성할 때 포함할 수도 있고 안할 수도 있다는 의미입니다.

# [ in WORDS ... ] 부분은 옵션입니다.
$ help for  
for: for NAME [in WORDS ... ] ; do COMMANDS; done

Shell builtins, keywords

help command

$ help set
set: set [-abefhkmnptuvxBCHP] [-o option-name] [--] [arg ...]
    Set or unset values of shell options and positional parameters.
...
...

외부 명령

  1. command --help

  2. man 섹션넘버 command

  3. info command

man page ( manual page ) 는 unix 에서 전통적으로 사용해온 문서 형식입니다.
뒤에 숫자를 이용해서 섹션별로 구분을 하는데 그 의미는 다음과 같습니다.

섹션 설명 예제
1 User Commands
2 System Calls man 2 write
3 C Library Functions man 3 printf
4 Devices and Special Files (usually found in /dev) man 4 tty
5 File formats and conventions e.g /etc/passwd, /etc/crontab man 5 proc
6 Games
7 Miscellaneous (including macro packages and conventions) man 7 signal
man 7 hier
8 System Administration tools and Deamons (usually only for root)  

"man 섹션넘버 intro" 로 각 섹션의 소개를 볼 수 있습니다.
http://man7.org/linux/man-pages/

man -f page

해당 페이지가 속한 섹션들을 짧은 설명과 함께 볼 수 있습니다.

$ man -f printf
printf (3)           - formatted output conversion
printf (1)           - format and print data

man -k regex

페이지 이름과 설명에서 단어를 검색할 수 있습니다.

$ man -k printf
asprintf (3)         - print to allocated string
dprintf (3)          - print to a file descriptor
fprintf (3)          - formatted output conversion
fwprintf (3)         - formatted wide-character output conversion
printf (1)           - format and print data
...
...

man page 는 문서에 별다른 기능이 없어서 복잡한 명령일 경우 내용이 빈약한것이 단점입니다. 그래서 1990년대 초에 GNU 에서 기존의 man page 를 대체하기 위해 나온것이 info 입니다. info 는 문서에 hyperlink 기능과 간단한 markup language 를 사용할 수 있어서 man page 보다 상세한 내용을 볼 수 있습니다.

$ info grep

Quiz

외부 명령의 존재 여부를 체크하려면 어떻게 할까요?

외부 명령의 존재 여부를 체크할 때 type, hash, command builtin 명령을 사용하는 것은 좋은 방법이 아닙니다. 왜냐하면 export 한 function 이나 alias 도 OK 가 됩니다. which 명령을 사용하거나 bash 의 경우 type -P 를 사용할 수 있습니다.

# vim 외부 명령이 존재하는지 체크

command=vim

if which "$command" > /dev/null; then ...

if type -P "$command" > /dev/null; then ...