명령어를 쉘이 해석하는 방법

Shell은 또 하나의 프로그램이고 이 프로그램은 사용자가 입력한 명령어들을 해석하며,일반적으로 4~5가지의 쉘이 주로 사용된다.

쉘이 하는 일

  • 쉘의 해석 과정
    -> 명령행을 단어로 분리
    -> 앨리어스히스토리 쉘 변수 및 환경변수들을 확장
    -> 표준 입력과 출력 스트림을 초기
  • 입력한 명령어를 쉘이 파싱하는 방법을 이해해야 진정한 파워유저가 될 수 있다.

bash의 소개

90년대 초까지 주로 C쉘이나 Bourn쉘을 이용하였다. 이후 무료로 쉽게 구할 수 있고, 더 풍부한 기능을 가진 tcsh과 bash가 주목을 받게 되었다.

ksh / csh이 제공하지 못하는 bash의 유용한 기능

  • 60개 이상의 쉘 변수
  • 원하는 정보를 쉘 프롬프트에 모아놓을 수 있다.
  • vi와 Emacs의 명령행 편집 기능

tcsh의 소개

C쉘과 상당히 유사하지만, 더 많은 기능들이 추가되었고, C쉘의 버그도 상당부분 해결하여 많은 인기를 얻었다.
명령어의 실행을 다시 한번 확인시켜주는 기능은 아래와 같이 나타난다.

$ rm *.c
Do you really want to delete all files? [n/y] n

오타도 자동으로 수정해준다.

$ who | srot +3n +4
CORRECT>who | sort +3n +4 (y|n|e|a)? y

명령행 해석

C쉘에 있는 여러가지 치환 매커니즘의 우선순위는 매우 중요하다. 다음은 C쉘이 명령행을 해석할 때의 치환 매커니즘의 순위이다.

    1. 히스토리 치환
    2. 단어(특수문자를 포함) 분리
    3. 히스토리 목록 갱신
    4. 단일 인용부호와('') 이중 인용부호("")의 해석
    5. 앨리어스 치환
    6. 표준 입출력의 리다이렉션(>, <, |)
    7. 변수 치환
    8. 명령어 치환
    9. 파일명 확장

(Bourn쉘은 히스토리 치환과 앨리어스 치환을 하지 않는다는 점을 제외하면 C쉘과 동일하다.)
히스토리 치환이 인용부호보다 순위가 앞서기 때문에 감탄문에서의 ! 조차 쉘은 히스토리 치환으로 알고 대치시킨다. 치환을 막기 위해서는 \!로 입력해주어야 한다.

예를 들어 정확히 이해해 보자.

$ ls -l $HOME/* |   grep "Mar 7"        # 홈 디렉토리에서 Mar 7을 포함하고 있는 파일의 정보를 표시
  1. 여기에는 히스토리 연산이 없으므로 생략
  2. 명령행을 빈칸으로 구분된 단어들로 분리한다. 분리된 단어는 ls, -l, $HOME/*, |, grep, “Mar 7”
  3. 쉘은 이 명령행을 히스토리 목록에 넣는다(Bourn은 이 과정이 없다.)
  4. “Mar 7”주위에 이중 인용부호가 있음을 인지하고 인용부호 내에서는 와일드카드 확장을 하지 않음을 인지한다.
  5. 쉘은 ‘ls’나 ‘grep’이 앨리어스가 아닌지 검사한다.
  6. 쉘이 ‘|‘를 보고 파이프를 초기화 하기 위한 작업들을 수행한다.
  7. 환경변수 ‘$HOME’을 보고 이를 해당 값(/Users/username)으로 대치시킨다.
  8. 쉘은 역인용부호(`)를 찾아 명령행에 그 결과를 추가한다.(위의 경우에는 해당없음)
  9. 와일드카드를 찾는다. 위의 경우 ‘*‘를 찾아 해당 파일명으로 대치시킨다.
  10. 쉘은 ‘ls’를 실행시키고 ‘ls’의 결과를 파이프를 통해 ‘grep’의 입력으로 보내어 ‘grep’을 실행시킨다.

명령행 인자 출력하기

echo 명령어는 뒤에 따라오는 인자들을 뉴라인을 붙여 출력한다. echo 명령은 변수의 값을 보거나 와일드카드가 붙은 파일명이 확장되면 어떤 이름이 되는지 확인하는데 또는 인용 처리 검사때 이용된다.

$ echo ~/*
/root/throughkim /root/work
$ echo -n ~/*
/root/throughkim /root/work $

echo 에 -n옵션을 붙이면 메시지 끝에 뉴라인이 붙지 않는다.

검색 경로 설정하기

검색 경로는 쉘이 외부 명령어를 찾아보는 순서화된 디렉토리 목록이다. 쉘 설정파일을 편집하여 로그인 할 때 마다 검색 경로가 설정되도록 할 수 있다.

$ vi ~/.profile

PATH=...

# C쉘

$ vi ~/.cshrc

set path=...

실행할 수 없는 명령어를 갖고 있는 디렉토리

만약 /usr/local/bin 디렉토리 안에 명령어들이 들어있고, 일부 컴퓨터에서만 실행되게 하고 싶다면 ~/no_run.hostname이라는 디렉토리를 만들고 그 안에 실행하지 말아야 할 명령어들을 집어넣는다. 이어서 PATH환경변수에 /usr/local/bin 보다 더 앞에 위치시킨다.

PATH = ... /root/no_run.hostname /usr/local/bin ...

앨리어스 속의 와일드 카드

다음은 명령행 파싱이 중요하다는 것을 보여주는 또다른 예이다. 파일의 단어 개수를 헤아리는 다음의 앨리어스를 보자.

$ alias words "wc -w *"

앨리어스를 치환하기 전에 쉘이 먼저 와일드카드를 확장해버리면 *는 결코 확장되지 않았을 것이다.

eval

다른 기회가 필요한 경우

$ set b=$a      #환경변수 b에 $a를 할당
$ set a=foo     #환경변수 a에 foo를 할당
$ echo $b       #b를 출력해보면
$a              #a를 프린트한다.

변수 치환은 한번 일어난다. 재귀적으로 발생하지 않는다. $b의 값은 $a일 뿐이다.

$ eval echo $b
foo

eval 명령어는 기회를 한번 더 달라, 이 라인을 다시 해석하고 실행하라 라는 의미이다. eval명령어를 사용하면 쉘은 명령행 해석을 다시한번 수행한다. 따라서 $b=$a=foo 가 되는 것이다.

“2>&1 file” 또는 “> file 2>&1”?

왜 두번재 명령어만 stdout과 stderr를 파일로 리다이렉트 하는 것인가?

$ cat food 2>&1 >file
cat: food: 그런 파일이나 디렉터리가 없습니다.
cat: file: 그런 파일이나 디렉터리가 없습니다.
$ cat food >file 2>&1
$
$ cat file
cat: food: 그런 파일이나 디렉터리가 없습니다.
  1. 첫 번째 명령에서 쉘은 2>&1을 먼저 본다. 이것은 “표준 에러(파일 기술어2)를 표준 출력(fd1)이 가는 곳으로 보내라” 는 뜻이다. 이미 fd1과 fd2는 모두 터미널로 가기 때문에 아무 효과가 없다. 그 뒤에 >file은 fd1(stdout)을 file로 리다이렉트 한다. 그러나 fd2(stderr)는 여전히 터미널로 간다.
  2. 두 번째 명령에서 쉘은 >file을 보고서 stdout을 file로 리다이렉트 한다. 그 후 2>&1은 fd2(stderr)를 fd1가 보내지는 같은 방향으로 보낸다. 즉 파일로 리다이렉트하는 것.

리눅스 입출력 표준 리다이렉션을 잠깐 짚어보고 간다.

쉘에서 키보드로 명령을 입력받는 것을 표준입력(stdin, 0), 키보드로 입력받은 명령의 실행결과를 모니터로 출력하는 것을 표준출력(stdout, 1), 에러의 결과를 모니터로 출력하는 것을 표준에러(stderr, 2)라고 한다.

쉘에서 명령의 결과를 모니터로 출력하지 않고 파일로 저장할 수 있는데 이 때 리다이렉션을 사용한다. 리다이렉션을 사용하여 출력과 입력의 방향을 지정해줄 수 있다.

리다이렉션 기호 방향 사용 의미
> 표준 출력 명령>파일 명령의 결과를 파일로 저장
>> 표준 출력(추가) 명령>>파일 명령의 결과를 기존 파일 데이터에 추가
< 표준 입력 명령<파일 파일의 데이터를 명령에 입력

기타 리다이렉션 기호의 쓰임은 아래와 같다.

사용 의미
명령>&파일 명령이 실행된 표준 출력의 결과와 에러를 파일로 출력
명령>!파일 파일의 존재유무와 상관없이 생성하고 표준출력의 결과를 파일로 출력
명령>&!파일 파일의 존재유무와 상관없이 생성하고 표준출력의 결과와 에러를 파일로 출력
명령A|명령B 명령A의 출력을 명령B의 입력으로 사용하여 실행
명령A|&명령B 명령A의 출력과 에러를 명령 B의 입력으로 사용하여 실행
$ ~/null 2>&1
# ~/null은 0 값을 갖는 null파일이고 표준에러(2)를 표준출력(1)로 리다이렉션 하라는 의미이다.

Bourn쉘의 인용 처리

  • \는 그 다음에오는 문자 하나의 특별한 의미를 무효화 한다.
  • ( ‘ )는 쌍을 이루는 다음 ( ‘ )이 나올 때 까지 모든 문자들의 특수 의미를 무효화 한다.
  • ( “ )는 $, `, \ 의 특수 의미를 보존한다.

Bourn쉘과 C쉘의 인용 처리 차이

  • ( ‘ )는 쌍을 이루는 다음 ( ‘ )이 나올 때 까지 !이외의 모든 문자들의 특수 의미를 무효화 한다.
  • ( “ )는 $, `, ! 의 특수 의미를 보존한다.

파일명 내의 특수문자를 인용 처리로 해결하기

$ touch 'a file with space'         #공백을 포함하는 파일을 생성
$ ll
drwxr-xr-x 2 root root 4096 12월 23 09:35 ./
drwx------ 8 root root 4096 12월 22 19:14 ../
-rw-r--r-- 1 root root    0 12월 23 09:35 a file with space
...

$ mv a\ file\ with\ space a_file_with_space     #공백을 언더바로 대체
$ ll
drwxr-xr-x 2 root root 4096 12월 23 09:38 ./
drwx------ 8 root root 4096 12월 22 19:14 ../
-rw-r--r-- 1 root root    0 12월 23 09:35 a_file_with_space
...

백슬래시 개수

$ echo hi \ there       #공백문자를 인용처리함
hi  there
$ echo hi \\ there      #\\가 \ 로 변환됨
hi \ there
$ echo hi \\\ there     #\\가 \로 변환되고 특수문자로 활용됨
hi \ there
through.kim's profile image

through.kim

2016-12-22 17:00

Read more posts by this author