자택경비대

MacOS 기본 bash 쉘 버전 업그레이드 하는법

Programming

우선 Homebrew 를 이용해 최신버전의 bash를 설치한다.

brew install bash

chsh 명령어를 이용해 기본 쉘을 변경할 수 있는데, brew로 설치한 shell은 등록이 되어있지 않기 때문에 오류가 발생한다.

chsh -s $(which bash)
# chsh: /usr/local/bin/bash: non-standard shell

따라서 다음과 같이 설치된 경로를 /etc/shells 파일에 추가하여 등록한 뒤에, chsh 명령어를 사용하면 해결된다.

sudo nano /etc/shells  # which bash 명령어의 결과값을 추가한다
chsh -s $(which bash)

Password: ...

2>&-, 2>/dev/null, |&, &>/dev/null and >/dev/null 2>&1 의 차이점에 대해서

stackoverflow

배경지식: 각 fd (file descriptor), 1stdout, 2stderr 그리고 0stdin 을 가리킨다.

N>&-: N에 해당하는 fd를 닫는다.
N>/dev/null: N에 해당하는 fd로 부터의 출력을 /dev/null로 리다이렉팅 한다.
N>&M: N의 출력을 M의 출력과 하나로 합친다.
|&: 2>&1 | 의 축약어이다. (bash4 버전부터 추가)
&>/dev/null: >/dev/null 2>&1 의 축약어이다.

Bash 파일 테스트 연산자

Programming
  • 아래의 조건이 일치할 경우 참을 반환한다.

-e: 파일이 존재할 경우
-a: -e 와 같다. (deprecated)
-f: 일반 적인 파일일 경우 (디렉토리, 디바이스 드라이버 파일의 경우 거짓)
-s: 파일의 크기가 0이 아닐경우
-d: 디렉토리인 경우
-b: 파일이 블록 디바이스인 경우
-c: 파일이 캐릭터 디바이스인 경우
-p: 파일이 파이프인 경우
-h, -L: 파일이 심볼릭링크인 경우
-S: 파일이 소켓인 경우
-t: fd가 터미널 디바이스와 관련이 있는경우
-r: 읽기 권한이 있는경우
-w: 쓰기 권한이 있는경우
-x: 실행 권한이 있는경우
-g: sgid 플래그가 설정되어 있는경우
-u: suid 플래그가 설정되어 있는경우
-k: stickybit가 설정되어 있는경우
-O: 파일의 소유자인 경우
-G: 파일과 같은 그룹인 경우
-N: 파일이 마지막으로 읽은 후에 수정된 경우
f1 -nt f2: f1 파일이 f2 파일보다 새로운 경우
f1 -ot f2: f1 파일이 f2 파일보다 오래된 경우
f1 -ef f2: f1 파일이 f2 파일과 같은경우 (hardlink)
!: not 연산자, 다른 연산자 앞에 쓰여서 반대 효과를 낼 수 있다.

Reference

Bash 매개변수 확장

Programming

${variable:-word}

$variable이 존재하지 않거나 null인 경우 word로 치환됩니다.

${variable:=word}

$variable이 존재하지 않거나 null일 경우 word값을 $variable에 대입하고 치환합니다.

${varaible:?word}

$variable이 존재하지 않거나 null일 경우 word 값을 stderr에 출력하고 종료합니다.

${variable:+word}

$variable이 존재하지 않거나 null일 경우 아무것도 하지 않습니다. 이외의 경우 word로 치환합니다


${variable:offset:length}

파이썬의 slice와 동일하게 동작합니다. 단, step이 존재하지 않습니다. 음수로 offset, length를 지정할 때에는 : 으로부터 공백을 추가 하는것을 권장합니다(:- 확장과 혼동할 수 있기 때문).

${!prefix*}
${!prefix@}

prefix로 시작하는 변수들을 IFS 변수로 구분하여 확장합니다.

${!variable[*]}
${!variable[@]}

$variable이 배열 변수일 경우 원소들을 IFS변수로 구분하여 확장합니다. 배열이 아니거나 null일 경우 0으로 확장합니다.

${variable#match}  # Shortest
${variable##match}  # Longtest

# e.g.)
FOO="foobarbaz"; echo ${FOO#*ba}
rbaz
FOO="foobarbaz"; echo ${FOO##*ba}
z

앞에서 부터 match와 동일한 부분을 삭제하여 확장합니다. #은 가장 짧은 경우, ##은 가장 긴 경우를 삭제합니다.

${variable%match}
${variable%%match}

# e.g.)
FOO="foobarbaz"; echo ${FOO%ba*}
foobar
FOO="foobarbaz"; echo ${FOO%%ba*}
foo

#와 같이 match되는 부분을 삭제하여 확장하지만, 뒤에서 부터 확인합니다.

${variable/pattern/word}

patternword로 치환하여 확장합니다. 만약 pattern/으로 시작할 경우 모든 패턴을 word로 치환합니다.

두개의 명령어 출력을 하나로 합치는 방법

Programming

예를 들어 echo fooecho bar, 두 개의 명령어의 출력을 하나로 합쳐서 cat으로 파이핑 하고 싶다면 아래와 같은 방법으로 해낼 수 있다.

# 방법 1
( echo foo ; echo bar ) | cat

# 방법 2
{ echo foo ; echo bar ; } | cat

bash에서 exec와 eval의 차이는 무엇일까?

stackoverflow

evalexec는 둘다 bash(1)의 내장 명령어 입니다.
exec가 몇 가지 다른 옵션을 가지고 있는것은 알지만 둘의 차이점은 무엇인가요?


evalexec는 완전히 다른 명령어라고 보셔도 됩니다.

$ help exec
exec: exec [-cl] [-a name] [command [arguments ...]] [redirection ...]
    Replace the shell with the given command.

exec cmd가 하는 일은, 그냥 cmd를 실행 했을때와 완전히 동일합니다.
다만, 다른 프로세스를 띄워서 명령어를 실행하는것이 아닌, 현재의 쉘을 명령어로 대체하게 됩니다.
내부적으로는, 예를들어 /bin/ls를 실행 할 경우 fork()를 호출하여서 자식 프로세스를 만든 후에 생성된 자식 프로세스 내에서 exec()를 재호출해서 /bin/ls를 실행하게 됩니다. exec /bin/ls는 이 과정(fork() 호출)을 거치지 않고 명령어를 실행하게 됩니다.

아래의 두 예제를 봅시다.
1.

$ bash -c 'echo $$ ; ls -l /proc/self ; echo foo'
7218
lrwxrwxrwx 1 root root 0 Jun 30 16:49 /proc/self -> 7219
foo

2.

$ bash -c 'echo $$ ; exec ls -l /proc/self ; echo foo'
7217
lrwxrwxrwx 1 root root 0 Jun 30 16:49 /proc/self -> 7217

echo $$ 명령어는 처음에 명령어를 호출한 쉘의 PID값을 출력하게 됩니다. 그리고 /proc/self를 대상으로 ls 명령어로 리스팅을 하게 되면 명령어를 실행하는 프로세스의 PID를 가져올 수 있습니다. 일반적으로, (1)에서 볼 수 있듯이 두 PID는 다른 값을 가지게 되지만, exec를 통해 실행된 명령은 같은 PID를 갖는 것을 알 수 있습니다.
또한, exec명령어가 현재의 쉘을 완전히 대체하였기 때문에, 뒤 따르는 echo foo 명령이 실행되지 않은 것도 확인할 수 있습니다.

한편,

$ help eval
eval: eval [arg ...]
    Execute arguments as a shell command.

eval명령어는 인자를 받아서 현재의 쉘에서 실행하게 됩니다. 쉽게 말하면 eval foo barfoo bar은 완전히 같다 볼 수 있습니다.
다만, 변수들이 명령어가 실행되기 전에 확장되어 변수에 담겨있는 명령어를 실행하는 것이 가능해집니다.

$ unset bar
$ cmd="bar=foo"
$ eval "$cmd"
$ echo "$bar"
foo