Bash는 수많은 Linux 및 Unix 계열 시스템의 기본 셸로 자리 잡고 있으며, 그 안에는 잘 알려지지 않은 다양한 기능이 숨어 있습니다. 그래서 무엇을 소개할지 고르기가 쉽지 않았지만, 결국 일상적인 작업을 더 편하게 만들어 주는 Bash 팁에 초점을 맞추기로 했습니다.
컨설턴트로 일하다 보면 다양한 환경과 업무 방식을 접하게 됩니다. 그 경험을 바탕으로 팁을 네 가지 큰 범주로 정리했습니다. 터미널과 라인 조작, 탐색과 파일 관리, 히스토리 활용, 유용한 명령어입니다. 물론 이 분류는 어디까지나 편의상 나눈 것일 뿐이며, 여러 범주에 걸칠 수 있는 팁도 많습니다.
그럼 지금부터 제가 만난 가장 유용한 Bash 트릭들을 소개하겠습니다.
Bash 히스토리 활용하기
생산성을 높이는 가장 좋은 방법 중 하나는 Bash 히스토리를 효과적으로 사용하는 것입니다. 특히 멀티 유저 환경이라면 histappend 옵션을 활성화하는 것이 가장 중요한 설정 중 하나입니다. 다음 명령어를 실행하기만 하면 됩니다.
shopt -s histappend 이 옵션을 사용하면 여러 터미널 세션이 동시에 히스토리 파일에 기록할 수 있습니다. 대부분의 환경에서는 이 옵션이 꺼져 있어서, 로컬이든 SSH든 Bash 세션을 두 개 이상 열면 히스토리가 자주 사라지곤 합니다.
또 하나 흔히 하는 작업은 마지막 명령어를 sudo로 다시 실행하는 것입니다. 예를 들어 mkdir /etc/ansible/facts.d를 실행했는데 root가 아니라면 실패합니다. 많은 사용자가 위쪽 화살표 키를 눌러 줄 맨 앞으로 이동한 뒤 sudo를 직접 입력하는데, 더 간단한 방법이 있습니다.
sudo !!Bash는 sudo에 이어 이전 명령어 전체를 실행해 줍니다. 실제 실행 결과는 다음과 같습니다.
[user@centos ~]$ mkdir -p /etc/ansible/facts.d
mkdir: cannot create directory ‘/etc/ansible’: Permission denied
[user@centos ~]$ sudo !!
sudo mkdir -p /etc/ansible/facts.d
!!가 실행되면 전체 명령이 터미널에 출력되므로 방금 어떤 명령이 실행됐는지 확인할 수 있습니다.
비슷하지만 덜 알려진 단축키로 !*가 있습니다. 이것은 이전 명령어의 *모든 인수*를 현재 명령어에 재사용하겠다는 의미입니다. 인수가 많은 명령어를 반복해서 쓸 때 유용합니다. 간단한 예로, 여러 파일을 만든 뒤 권한을 변경하는 경우입니다.
[user@centos tmp]$ touch file1 file2 file3 file4
[user@centos tmp]$ chmod 777 !*
chmod 777 file1 file2 file3 file4
특정 상황에서만 유용하지만 타이핑 수고를 덜어 줍니다.
타이핑을 아낀다면 히스토리에서 명령어를 찾는 방법도 이야기해 볼 만합니다. 대부분의 사용자는 이렇게 검색합니다.
history |grep <some command>하지만 더 쉬운 방법이 있습니다. 다음 키를 눌러 보세요.
ctrl + rBash가 히스토리를 역방향으로 검색합니다. 타이핑을 시작하면 결과가 점점 나타납니다.
(reverse-i-search)`hist': shopt -s histappend위 예제에서는 hist를 입력했더니 앞서 살펴본 shopt 명령어와 일치했습니다. ctrl + r을 계속 누르면 다른 일치 항목들을 차례로 거슬러 올라가며 검색합니다.
마지막 트릭은 사실 트릭이라기보다, 히스토리에서 가장 많이 사용된 명령어를 세어 보여 주는 유용한 명령어입니다.
[user@centos tmp]$ history | awk 'BEGIN {FS="[ \t]+|\\|"} {print $3}' | sort | uniq -c | sort -nr | head
81 ssh
50 sudo
46 ls
45 ping
39 cd
29 nvidia-xrun
20 nmap
19 export이 예제에서는 ssh가 현재 히스토리에서 가장 많이 사용된 명령어임을 알 수 있습니다.
탐색과 파일 이름 다루기
명령어, 파일 이름, 폴더 이름을 입력한 뒤 tab 키를 한 번 누르면 자동 완성된다는 것은 이미 잘 알고 있을 겁니다. 일치 항목이 하나뿐일 때 동작합니다. 하지만 tab을 두 번 누르면 입력한 내용에 해당하는 모든 후보가 표시된다는 점은 모르는 분도 많습니다.
[user@centos tmp]$ cd /lib <tab><tab>
lib/ lib64/
파일 시스템을 탐색할 때 매우 유용합니다. 또 하나 도움이 되는 트릭은 cdspell 옵션을 켜는 것입니다. shopt -s cdspell 명령어로 활성화하면 오타를 자동으로 교정해 줍니다.
[user@centos etc]$ cd /tpm
/tmp
[user@centos tmp]$ cd /ect
/etc
완벽하지는 않지만, 작은 도움도 큰 도움입니다!
디렉터리를 성공적으로 변경한 뒤 이전 디렉터리로 돌아가야 한다면 어떨까요? 디렉터리 깊이가 얕으면 문제가 없지만, /var/lib/flatpak/exports/share/applications/처럼 깊은 경로라면 이렇게 입력해야 할 겁니다.
cd /va<tab>/lib/fla<tab>/ex<tab>/sh<tab>/app<tab>다행히 Bash는 이전 디렉터리를 기억하고 있으며, cd -만 입력하면 바로 돌아갈 수 있습니다.
[user@centos applications]$ pwd
/var/lib/flatpak/exports/share/applications
[user@centos applications]$ cd /tmp
[user@centos tmp]$ pwd
/tmp
[user@centos tmp]$ cd -
/var/lib/flatpak/exports/share/applications
여기서 한 발 더 나아가, 자주 오가는 여러 디렉터리를 손쉽게 이동하고 싶다면 변수 하나를 설정하면 됩니다.
[user@centos applications]$ export CDPATH='~:/var/log:/etc'
[user@centos applications]$ cd hp
/etc/hp
[user@centos hp]$ cd Downloads
/home/user/Downloads
[user@centos Downloads]$ cd ansible
/etc/ansible
[user@centos Downloads]$ cd journal
/var/log/journal
위 예제에서는 홈 디렉터리(틸드 ~)와 /var/log, /etc를 설정했습니다. 이 디렉터들의 최상위 항목은 이름만 언급하면 자동으로 찾아집니다. 단, CDPATH에 등록된 디렉터의 최상위에 있지 않은 디렉터는 찾지 못합니다. 예를 들어 목적지가 /etc/ansible/facts.d/라면 cd facts.d로는 이동할 수 없습니다. ansible은 /etc 아래에 있지만 facts.d는 아니기 때문입니다. 따라서 CDPATH는 자주 방문하는 트리의 최상단으로 빠르게 이동할 때 유용하지만, 깊은 폴더 구조를 탐색할 때는 관리가 번거로울 수 있습니다.
마지막으로 누구나 한 번쯤 겪는 두 가지 상황을 다뤄 보겠습니다. 바로 파일 확장자 변경과 파일 이름 일괄 변경입니다. 비슷해 보이지만 Bash는 이 작업들을 위해 몇 가지 서로 다른 트릭을 제공합니다.
조금 투박한 작업이긴 해도, 작업 중인 파일의 빠른 백업 복사본이 필요할 때가 있습니다. 대부분 파일 이름을 그대로 복사한 뒤 .old나 .bak 같은 확장자를 붙입니다. 예를 들어 spideroak_inotify_db.07pkh3라는 파일의 복사본을 남기고 싶다면 이렇게 입력할 수 있습니다.
cp spideroak_inotify_db.07pkh3 spideroak_inotify_db.07pkh3.bak복사·붙여넣기, tab 자동 완성, 인수 반복 단축키 등을 활용할 수도 있지만, 익숙해지면 아래 명령어가 훨씬 빠릅니다.
cp spideroak_inotify_db.07pkh3{,.old}예상대로 이 명령은 파일 끝에 .old 확장자를 붙여 복사합니다. 그런데 파일을 한 번에 대량으로 이름 변경하고 싶다면 어떻게 할까요? for 루프를 작성할 수도 있지만(복잡한 작업에서는 실제로 저도 그렇게 합니다), rename이라는 편리한 유틸리티가 있는 굳이 그럴 필요가 없습니다. 다만 Debian/Ubuntu와 CentOS/Arch에서 사용법이 조금 다릅니다. Debian 계열의 rename은 SED와 비슷한 문법을 사용합니다.
user@ubuntu-1604:/tmp$ for x in `seq 1 5`; do touch old_text_file_${x}.txt; done
user@ubuntu-1604:/tmp$ ls old_text_file_*
old_text_file_1.txt old_text_file_3.txt old_text_file_5.txt
old_text_file_2.txt old_text_file_4.txt
user@ubuntu-1604:/tmp$ rename 's/old_text_file/shiney_new_doc/' *.txt
user@ubuntu-1604:/tmp$ ls shiney_new_doc_*
shiney_new_doc_1.txt shiney_new_doc_3.txt shiney_new_doc_5.txt
shiney_new_doc_2.txt shiney_new_doc_4.txtCentOS나 Arch에서는 다음과 같습니다.
[user@centos /tmp]$ for x in `seq 1 5`; do touch old_text_file_${x}.txt; done
[user@centos /tmp]$ ls old_text_file_*
old_text_file_1.txt old_text_file_3.txt old_text_file_5.txt
old_text_file_2.txt old_text_file_4.txt
[user@centos tmp]$ rename old_text_file centos_new_doc *.txt
[user@centos tmp]$ ls centos_new_doc_*
centos_new_doc_1.txt centos_new_doc_3.txt centos_new_doc_5.txt
centos_new_doc_2.txt centos_new_doc_4.txtBash 키 바인딩
Bash에는 내장 키보드 단축키가 많습니다. bind -p를 입력하면 전체 목록을 확인할 수 있습니다. 몇몇은 이미 잘 알려져 있겠지만, 유용한 것들을 골라 소개합니다.
- ctrl + _ (실행 취소)
- ctrl + t (두 문자 위치 교환)
- ALT + t (두 단어 위치 교환)
- ALT + . (이전 명령어의 마지막 인수 출력)
- ctrl + x + * (glob/별표 확장)
- ctrl + 화살표 (한 단어 앞으로 이동)
- ALT + f (한 단어 앞으로 이동)
- ALT + b (한 단어 뒤로 이동)
- ctrl + x 이후 ctrl + e (명령어를 에디터에서 열어 실행 전에 수정)
- ctrl + e (커서를 줄 끝으로 이동)
- ctrl + a (커서를 줄 시작으로 이동)
- ctrl + xx (줄의 반대편 끝으로 이동)
- ctrl + u (커서 앞부분 전체 잘라내기)
- ctrl + k (커서 뒷부분 전체 잘라내기)
- ctrl + y (버퍼에서 붙여넣기)
- ctrl + l (소문자 L, 화면 지우기)
너무나 뻔한 단축키들은 생략하겠습니다. 그중에서도 가장 유용하다고 느낀 것은 단어(또는 텍스트 구간)를 삭제하고 되돌릴 수 있는 단축키들입니다. 예를 들어 systemd로 여러 서비스를 중지시켰다가, 어떤 작업이 끝난 뒤 그중 일부만 다시 시작하고 싶다고 가정해 봅시다.
systemctl stop httpd mariadb nfs smbd
<위쪽 화살표로 이전 명령 불러오기>
<'ctrl + w'로 불필요한 인수 삭제>
그런데 하나를 너무 많이 지웠다면? 걱정 없습니다. ctrl + _로 마지막 편집을 되돌리면 됩니다.
다른 잘라내기 명령들을 사용하면 커서에서 줄 끝까지(Ctrl + k), 또는 커서에서 줄 시작까지(Ctrl + u) 한 번에 지울 수 있습니다. 잘라낸 텍스트가 터미널 버퍼에 저장되므로 나중에 ctrl + y로 붙여넣을 수 있다는 장점도 있습니다. 이런 명령들은 글로 설명하기 어려우니 직접 사용해 보시기를 강력히 권합니다.
마지막으로, 컨테이너처럼 제약이 많은 환경에서 매우 유용한 드물게 쓰이는 키 조합을 소개합니다. 이전 출력 때문에 명령어가 뒤엉켜 보인다면 ctrl + x + ctrl + e를 눌러 보세요. 환경 변수 EDITOR에 설정된 에디터에서 명령어가 열립니다. 길거나 뒤엉킨 명령어를 텍스트 줄바꿈이 가능한 에디터에서 편안하게 수정할 수 있습니다. 일반 파일을 다루듯 저장하고 종료하면 에디터를 나가는 즉시 명령이 실행됩니다.
기타 유용한 팁
Bash 셸에 색상을 표시하면 사용 경험이 한층 좋아질 수 있습니다. 색상이 꺼진 세션을 사용 중이라면 아래 명령어들을 .bash_profile에 추가해 보세요. 내용이 단순해서 자세한 설명은 필요 없을 겁니다.
# enable colors
eval "`dircolors -b`"
# force ls to always use color and type indicators
alias ls='ls -hF --color=auto'
# make the dir command work kinda like in windows (long format)
alias dir='ls --color=auto --format=long'
# make grep highlight results using color
export GREP_OPTIONS='--color=auto'
# Add some colour to LESS/MAN pages
export LESS_TERMCAP_mb=$'\E[01;31m'
export LESS_TERMCAP_md=$'\E[01;33m'
export LESS_TERMCAP_me=$'\E[0m'
export LESS_TERMCAP_se=$'\E[0m'
export LESS_TERMCAP_so=$'\E[01;42;30m'
export LESS_TERMCAP_ue=$'\E[0m'
export LESS_TERMCAP_us=$'\E[01;36m'
Bash의 각종 옵션을 조정하는 것 외에도 시간을 절약해 주는 요령들이 있습니다. 예를 들어 종료 상태와 관계없이 두 명령어를 연달아 실행하려면 ;로 구분하면 됩니다.
[user@centos /tmp]$ du -hsc * ; df -h이 명령은 현재 디렉터리의 각 파일이 차지하는 용량(및 합계)을 계산한 뒤, 블록 장치별 디스크 사용량을 조회합니다. du 명령에서 오류가 발생해도 두 번째 명령은 실행됩니다.
첫 번째 명령이 성공했을 때만 다음 작업을 진행하고 싶다면 어떻게 할까요? &&를 사용하면 첫 번째 명령이 성공적인 종료 상태를 반환할 때만 두 번째 명령이 실행됩니다. 예를 들어 업데이트가 성공했을 때만 재부팅하고 싶다면:
[root@arch ~]$ pacman -Syu --noconfirm && reboot명령어를 실행하면서 출력을 캡처해야 할 때가 있습니다. 대부분 표준 출력을 터미널과 파일에 동시에 기록하는 tee 명령어를 알고 있습니다. 하지만 strace처럼 복잡한 출력을 캡처하려면 I/O 리디렉션을 활용해야 합니다. I/O 리디렉션의 세부 사항은 이 글의 범위를 벗어나지만, 우리가 신경 쓸 부분은 STDOUT과 STDERR입니다. 보이는 것을 그대로 캡처하는 가장 좋은 방법은 둘을 하나의 파일로 합치는 것이며, 이때 2>&1 리디렉션을 사용합니다.
[root@arch ~]$ strace -p 1140 > strace_output.txt 2>&1이렇게 하면 관련 출력 전체가 strace_output.txt 파일에 저장되어 나중에 확인할 수 있습니다.
오래 실행되는 명령어 도중 작업을 잠시 멈춰야 할 때가 있습니다. 'stop' 단축키 ctrl + z를 사용하면 작업을 종료하지 않고(kill하지 않고) 중지할 수 있습니다. 작업은 작업 큐에 추가되지만, 재개하기 전까지는 보이지 않습니다. 이후 포그라운드 명령어 fg로 작업을 다시 이어갈 수 있습니다.
또한 ctrl + s로 작업을 일시 정지할 수도 있습니다. 이 경우 작업과 출력은 터미널 포그라운드에 그대로 머물며, 셸 사용권은 사용자에게 돌아오지 않습니다. ctrl + q를 누르면 작업이 재개됩니다.
터미널을 여러 개 띄워 놓은 그래픽 환경에서 작업한다면 출력을 복사·붙여넣는 단축키가 유용합니다.
# Copies highlighted text
ctrl + shift + c
# Pastes text in buffer
ctrl + shift + v
실행 중인 명령어의 출력에서 다른 명령어가 실행되는 것을 보고 자세한 정보를 알고 싶다면 몇 가지 방법이 있습니다. 해당 명령어가 PATH 어딘가에 있다면 which 명령어로 디스크상의 위치를 확인할 수 있습니다.
[root@arch ~]$ which ls
/usr/bin/ls
이 정보를 바탕으로 file 명령어로 바이너리를 검사할 수 있습니다.
[root@arch ~]$ file /usr/bin/ls
/usr/bin/ls: ELF 64-bit LSB pie executable x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=d4e02b88e596e4f82c6cc62a5bc4ce5827209a49, stripped
여러 정보를 볼 수 있지만 대부분의 사용자에게 가장 중요한 것은 ELF 64-bit LSB라는 부분입니다. 이것은 스크립트가 아닌 미리 컴파일된 바이너리라는 의미입니다. 명령어를 검사하는 관련 도구로 command 자체도 있습니다. command -V <command>를 실행하면 다양한 정보를 얻을 수 있습니다.
[root@arch ~]$ command -V ls
ls is aliased to `ls --color=auto`
[root@arch ~]$ command -V bash
bash is /usr/bin/bash
[root@arch ~]$ command -V shopt
shopt is a shell builtin
마지막으로 개인적으로 가장 좋아하는 트릭 중 하나인 echo 명령어를 소개합니다. 특히 컨테이너 작업이나 내가 잘 모르고 통제할 수 없는 환경에서 빛을 발합니다. for 루프가 예상한 시퀀스대로 돌아가는지 확인하는 것부터 원격 포트가 열려 있는지 점검하는 것까지 가능합니다. 포트 검사 문법은 아주 간단합니다. echo > /dev/<udp or tcp>/<server ip>/<port>
user@ubuntu-1604:~$ echo > /dev/tcp/192.168.99.99/222
-bash: connect: Connection refused
-bash: /dev/tcp/192.168.99.99/222: Connection refused
user@ubuntu-1604:~$ echo > /dev/tcp/192.168.99.99/22
시도하려는 연결 유형에 대해 포트가 닫혀 있으면 Connection refused 메시지가 출력되고, 패킷이 성공적으로 전송되면 아무 출력도 없습니다.
이 팁들이 Bash를 더 효율적이고 즐겁게 사용하는 데 도움이 되기를 바랍니다. 여기서 소개한 것 외에도 Bash에는 숨겨진 트릭이 무궁무진합니다. 여러분의 애용하는 팁은 무엇인가요?
부록 1. 다룬 팁과 트릭 목록
# History related
ctrl + r (reverse search)
!! (rerun last command)
!* (reuse arguments from previous command)
!$ (use last argument of last command)
shopt -s histappend (allow multiple terminals to write to the history file)
history | awk 'BEGIN {FS="[ \t]+|\\|"} {print $3}' | sort | uniq -c | sort -nr | head (list the most used history commands)
# File and navigation
cp /home/foo/realllylongname.cpp{,-old}
cd -
rename 's/text_to_find/been_renamed/' *.txt
export CDPATH='/var/log:~' (variable is used with the cd built-in.)
# Colourize bash
# enable colors
eval "`dircolors -b`"
# force ls to always use color and type indicators
alias ls='ls -hF --color=auto'
# make the dir command work kinda like in windows (long format)
alias dir='ls --color=auto --format=long'
# make grep highlight results using color
export GREP_OPTIONS='--color=auto'
export LESS_TERMCAP_mb=$'\E[01;31m'
export LESS_TERMCAP_md=$'\E[01;33m'
export LESS_TERMCAP_me=$'\E[0m'
export LESS_TERMCAP_se=$'\E[0m' # end the info box
export LESS_TERMCAP_so=$'\E[01;42;30m' # begin the info box
export LESS_TERMCAP_ue=$'\E[0m'
export LESS_TERMCAP_us=$'\E[01;36m'
# Bash shortcuts
shopt -s cdspell (corrects typoos)
ctrl + _ (undo)
ctrl + arrow (move forward a word)
ctrl + a (move cursor to start)
ctrl + e (move cursor to end)
ctrl + k (cuts everything after the cursor)
ctrl + l (clears screen)
ctrl + q (resume command that is in the foreground)
ctrl + s (pause a long running command in the foreground)
ctrl + t (swap two characters)
ctrl + u (cuts everything before the cursor)
ctrl + x + ctrl + e (opens the command string in an editor so that you can edit it before it runs)
ctrl + x + * (expand glob/star)
ctrl + xx (move to the opposite end of the line)
ctrl + y (pastes from the buffer)
ctrl + shift + c/v (copy/paste into terminal)
# Running commands in sequence
&& (run second command if the first is successful)
; (run second command regardless of success of first one)
# Redirecting I/O
2>&1 (redirect stdout and stderr to a file)
# check for open ports
echo > /dev/tcp/<server ip>/<port>
`` (use back ticks to shell out)
# Examine executable
which <command>
file <path/to/file>
command -V <some command binary> (tells you whether <some binary> is a built-in, binary or alias)