Command History

터미널을 열었을때 실행되는 interactive shell 에서는 명령 history 기능을 사용할 수 있습니다. 명령 history 는 이전에 한번 사용했던 명령을 다시 타입 할 필요 없이 재사용할 수 있게 해줍니다. 터미널 별로 history list 가 생성되며 사용한 명령들이 목록에 추가됩니다. 터미널 종료 시에는 shopt -s histappend 옵션 설정에 따라 $HISTFILE 에 현재 history list 가 저장됩니다.

history 확장에 사용되는 문자는 ! 인데 명령 행상 어느 위치에서든지 ! 문자에 이어 공백 없이 다른 문자나 숫자가 오면 history 확장이 됩니다. 심지어 double quotes 안에서도 확장이 일어나므로 주의해야 합니다. 한가지 예외적인 경우는 != 인데 [ 명령에서 연산자로 사용되므로 history 확장에서 제외됩니다. ! 문자가 명령이름의 위치에 오고 뒤이어 공백이 올경우는 shell keyword 로 인식되어 logical NOT 의 기능을 합니다.

$ find ! -size 0        # ! 문자 뒤에 공백이 오므로 OK

$ find !-size 0         # 이것은 history 확장 대상으로 명령이 정상적으로 실행되지 않는다.
bash: !-size: event not found

$ ! test -s emptyfile   # logical NOT 쉘 키워드 (! 문자가 명령 위치에 오고 이어 공백이 오므로)

Command history 확장은 shell prompt 상에서만 작동하는 기능입니다.
그러므로 Non-interactive shell 인 스크립트 실행시에는 기본적으로 disable 됩니다.

명령 라인을 찾는 방법

프롬프트에서 history 명령을 실행하면 현재 history list 에 있는 목록들이 번호와 함께 표시됩니다. 이 번호는 해당 명령 라인을 지정할때 사용됩니다.

!n

n 번 명령을 리턴합니다.

$ !2145
lsb_release -d
Description:    Ubuntu 15.04

$ echo command history : !2145
command history : lsb_release -d

!!

바로 이전 명령을 나타냅니다.

$ history
...
    3  ps f
    4  cat README.md
    5  find * -name '*.log' -size +10M -exec rm -f {} \;
$ !!
find * -name '*.log' -size +10M -exec rm -f {} \;

!-n

이전 n 번째 명령을 나타냅니다.

$ history
...
    3  date
    4  ps f
    5  find * -name '*.log'

$ !-1
find * -name '*.log'

$ !-2
ps f

$ !-3
date

!string

명령 이름이 string 으로 시작하는 가장 최근 명령을 찾습니다.

$ history
...
    3  find * -name '*.log' -size +10M -exec rm -f {} \;
    4  find * -name '*.log'
    5  ps f

$ !fi
find * -name '*.log'

!?string[?]

이건 명령이름을 검색하는게 아니고 전체 명령라인 중에 string 이 포함돼 있는지를 찾습니다. 가장 최근에 매칭이 되는 라인을 리턴합니다.

$ history
...
    3  find * -name "*.tmp" -o -name "*.old"
    4  find * -name "*.tmp"
    5  find * -name "*.log"

$ !?tmp
find * -name "*.tmp"

# 뒤에 '?' 를 붙이면 연이어 명령을 작성할 수 있다.
$ !?tmp? -exec rm -f {} \;
find * -name "*.tmp" -exec rm -f {} \;

# 뒤에 '?' 를 안붙일 경우 오류
$ !?tmp -exec rm -f {} \;
bash: !?tmp -exec rm -f {} \;: event not found

^old^new

이전 명령에서 처음에 매칭되는 하나만 변경됩니다. !!:s/string1/string2/ 와 같습니다.

$ mkdir -p test/exp/scenario/

$ ^exp^lab  

$ mkdir -p test/lab/scenario/

!#

이것은 이전 명령이 아니라 현재 프롬프트 상에서 작성 중인 명령을 나타냅니다.

$ echo 111 222 333 !#:1  # 첫번째 인수
$ echo 111 222 333 111

$ echo 111 222 333 !#:2  # 두번째 인수
$ echo 111 222 333 222

$ mv long/path/name/oldname !#$    # 마지막 인수
$ mv long/path/name/oldname long/path/name/oldname

찾은 명령 라인에서 원하는 인수를 지정하는 방법

명령 라인을 지정한 후에 : 문자를 붙인 후 원하는 인수들을 지정할 수 있습니다. 0 번은 명령을 나타내고 이후 인수들은 1, 2, 3 ... 번으로 지정할 수 있습니다.

$ touch home foo bar tmp log

$ !tou:0        # 0 번은 명령
touch

$ !tou:1        # 1 번은 첫번째 인수
home

$ !tou:2        # 2 번은 두번째 인수
foo

$ !tou:2-4      # '-' 를 이용해 범위를 지정
foo bar tmp

$ !tou:*        # '*' 는 모든 인수를 나타냅니다.
home foo bar tmp log

$ !tou:3*       # '3*' 은 3 번째 인수부터 끝까지
bar tmp log

$ !tou:^        # '^' 는 첫번째 인수를 나타내며 !tou:1 와 같습니다. 
home

$ !tou:$        # '$' 는 마지막 인수를 나타냅니다.
log

명령라인 지정을 생략하면 바로 이전 명령이 사용됩니다.

$ history
...
    3  date
    4  ps f
    5  echo 11 22 33 44

$ !:0
echo 

$ !:1
11

$ !:2-4
22 33 44

$ !*
11 22 33 44

$ !^
11

$ !$
44

지정한 라인, 인수에 modifiers 적용하기

라인을 지정한 뒤, 또는 인수들을 지정한 뒤에 : 문자를 붙인 후 modifiers 를 적용시킬 수 있습니다.

s/old/new/

지정한 명령라인에서 old 에 해당하는 스트링을 new 로 변경합니다. new 부분에 & 문자가 오면 old 로 대체됩니다. 기본적으로 처음에 매칭되는 하나만 적용되며 모두에 적용하려면 g 옵션을 추가합니다.

$ touch foo1 bar1 tmp1

# 처음 하나만 변경된다
$ !:s/1/2
touch foo2 bar1 tmp1

# 'g' 옵션을 추가하면 모두 변경된다.
$ !:gs/1/2
touch foo2 bar2 tmp2

파일 경로명에서 파일명 분류하기

$ cat /home/foo/readme.txt

$ !:h                   # 'h' 는 head 를 의미
cat /home/foo           # cat 명령이 함께 포함됨

$ !:t                   # 't' 는 tail 을 의미
readme.txt

$ !:h/AA                # 연이어 명령을 작성할 수 있다.
cat /home/foo/AA

$ !:1:h                 # ':1' 인수에서 head 를 구함
/home/foo

파일 경로명에서 확장자 분류하기

$ cat /home/foo/readme.txt

$ !:r                        # 확장자를 remove
cat /home/foo/readme

$ !:r.old
cat /home/foo/readme.old

$ !:e                        # 확장자 (extension) 만 구함.
.txt

결과물 quoting 하기

$ echo foo bar tmp

$ !:2-3:q
'bar tmp'

$ !:q                       # 전체 라인이 quote 된다.
'echo foo bar tmp'

$ !:*:x                     # space 로 분리되어 각각 quote 된다.
'foo' 'bar' 'tmp'

실행 금지하기

history 확장이 되면 바로 결과물이 실행되는데 p 옵션을 붙이면 결과만 표시하고 실행을 금지할 수 있습니다.

$ mv foo bar

$ !mv:s/foo/boo/:p      # 결과만 프린트되고 실행은 되지 않는다
mv boo bar

Double quotes 과 history 확장

History 확장은 double quotes 내에서도 일어나므로 주의해야 합니다.

$ lsb_relase -d
Description:    Ubuntu 15.04

$ echo command history : !lsb
command history : lsb_release -d

$ echo "hello!lsb world"          # double quotes 에서도 history 확장이 된다.
hellolsb_relase -d world"

$ echo "hello!516world"            
hellolsb_release -dworld   

$ echo 'hello!516world'           # single quotes 에서는 확장이 안된다.
hello!516world

Double quotes 사용시 다음과 같이 history 확장을 회피할 수 있습니다.

$ echo "hello"\!"lsb world"      
hello!lsb world

$ edho "hello"'!'"516world"
hello!516world

# sed 명령으로 "2016 12-10" 와 매칭되지 않는 라인을 삭제하려고 하지만 !d 에서
# history 확장이 되어 정상적으로 실행되지 않는다.
$ search="2016 12-10"
$ sed "/$search/!d" file.txt
ERR
$ sed "/$search/"'!d' file.txt

# 다음과 같은 경우도 double quotes 내에 있으므로 history 확장 대상입니다.
$ AA="$( echo 111 | sed -r '/222/!{ s/.*/xxx/ }' )"
bash: !{s/.*/xxx/}': event not found

$ AA="$( echo 111 | sed -r '/222/'\!'{ s/.*/xxx/ }' )"
$ echo $AA
xxx

History 관련 환경 변수

  • HISTIGNORE

    history 리스트에 저장할때 제외시킬 명령패턴을 : 로 분리하여 등록합니다.

    HISTIGNORE='ls:ls -al:cd:bg:fg:history'
    
  • HISTFILESIZE

    history 파일에 저장될 최대 라인수를 나타냅니다.

  • HISTSIZE

    history 리스트에 기억될 최대 명령수를 나타냅니다. 디폴트 값은 500 입니다.

  • HISTFILE

    history 를 저장할 파일을 지정합니다.

  • HISTCONTROL

    명령 history 의 작동방식을 : 로 분리하여 설정할수 있습니다.

    • ignorespace : space 로 시작하는 명령라인을 history 에 저장하지 않습니다.
    • ignoredups : 이전 history 명령과 중복될경우 저장하지 않습니다.
    • ignoreboth : ignorespace:ignoredups 와 같습니다.
    • erasedups : 이전 모든 history 라인을 비교하여 중복된 history 를 제거한후 저장합니다.
  • HISTTIMEFORMAT

    history 번호에 이어 timestamp 를 붙일 수 있습니다. history file 에도 저장됩니다.
    예) export HISTTIMEFORMAT="%F %T "

History 관련 옵션

Set

  • history

    명령 history 기능을 enable, disable 할 수 있습니다.

  • -H | histexpand

    ! 문자를 이용한 history 확장 기능을 제공합니다. set -o history 이 설정돼있어야 사용할 수 있습니다.

Shopt

  • cmdhist

    multiple-line 명령을 작성할 경우 명령 줄들이 각각 다른 history 번호로 할당돼서 다음에 재사용하기가 어려운데 이 옵션을 사용하면 newline 을 ; 로 치환해서 저장해 줍니다. lithist 옵션과 같이 사용하면 newline 도 그대로 유지됩니다.

  • lithist

    multiple-line 명령을 작성할 경우 newline 을 유지해 줍니다.

  • histreedit

    history 확장이 실패할 경우 입력했던 내용이 없어지지 않고 다시 수정할수 있는 기회를 줍니다.

  • histverify

    history 확장된 명령을 바로 실행하지 않고 필요시 수정할 수 있게 enter 를 칠 기회를 줍니다.

  • histappend

    shell 을 exit 할때 HISTFILE 변수에 설정돼 있는 파일에 현재 history list 를 append 합니다. off 이면 overwrite 합니다.

    터미널이 비정상적으로 종료할 경우 history list 가 저장되지 않습니다. 그럴 경우를 위해 PROMPT_COMMAND='history -a' 를 설정해 사용할 수 있습니다.

History builtin 명령

history [-c] [-d offset] [n] or history -anrw [filename] or history -ps arg [arg...]

현재 세션의 history list 를 관리하며 history file 을 read 하거나 write 해서 여러 터미널 세션 간에 history 를 동기화할 수 있습니다.

옵션 설명
-c 현재 세션의 history list 를 모두 삭제합니다.
-d offset offset 위치의 항목을 삭제합니다.
-r history file 을 읽어들이고 내용을 현재 세션의 history list 에 append 합니다
-n history file 에서 아직 읽어 들이지 않은 항목이 있으면 모두 읽어 들입니다.
-a 현재 세션의 history list 를 history file 에 append 합니다.
-w 현재 세션의 history list 를 history file 에 write 합니다.