Tips

대입 연산을 할 땐 변수를 quote 하지 않아도 된다.

보통 변수나 명령치환을 명령문에서 quote 하지않고 사용하게 되면 단어분리, globbing 이 일어나고 공백과 개행이 유지되지 않습니다. 그런데 예외가 있는데요 바로 = , += 대입연산을 할 때입니다. 대입연산에서는 quote 하지 않아도 단어분리, globbing 이 일어나지 않고 포맷을 그대로 유지해 줍니다.

대입연산은 처리되는 시점이 키워드와 같습니다. 그러므로 명령이 실행될때 shell 에의해 단계적으로 처리되는 brace 확장, 단어분리, globbing 에 영향을 받지 않습니다.

$ AA="I
> like
> winter     and     snow"

$ echo $AA       # 변수를 quote 하지 않으면 공백과 개행이 유지되지 않는다.
I like winter and snow

$ BB=$AA         # 대입연산 에서는 변수를 quote 하지 않아도 된다.

$ echo "$BB"     # 공백과 개행이 유지 된다.
I
like
winter     and     snow

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

$ AA="foo          "
$ BB=bar
$ CC=$AA$BB              # quote 을 하지 않아도 된다.
$ echo "$CC"             # 공백이 유지 된다.
foo          bar

$ DD=/home/test/$AA$BB
$ echo "$DD"
/home/test/foo          bar

$ EE=$(echo "foo          bar")
$ echo "$EE"
foo          bar

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

$ ARR=(11 22 33 44 55)
$ AA=${ARR[*]}    # array 원소들이 공백으로 분리돼 있지만 정상적으로 대입된다.
$ echo "$AA"
11 22 33 44 55

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

$ AA=*           # 대입연산 에서는 globbing 이 일어나지 않는다.
$ echo "$AA"
*

# 다음과 같은 경우 SORTED_FILES 대입연산 에서는 globbing 이 일어나지 않으므로
# 값이 myfile.split.*.sorted 가 되지만 rm 명령에서는 globbing 이 일어나므로 
# 매칭 되는 파일들이 모두 삭제됩니다.

FILE_PREFIX=myfile.split
SORTED_FILES=$FILE_PREFIX.*.sorted

rm -f $SORTED_FILES

명령에 선행하는 대입 연산

명령에 선행한 대입 연산은 명령이 실행되기 위해 fork-exec 과정을 거칠때 export 한 변수와 같이 인수로 전달됩니다. 그러므로 결과적으로 해당 변수는 해당 명령 이하에만 적용이 되고 명령이 종료된 후에는 사라집니다.

read 명령은 IFS 값을 이용해 읽어들인 라인의 필드를 분리하는데 다음과 같이 명령 앞에서 변경하여 사용하면 종료후에 IFS 값을 복구하는 과정을 거치지 않아도 됩니다.

$ echo -n "$IFS" | od -a       # read 명령 사용전 IFS 값
0000000  sp  ht  nl

# IFS 값을 read 명령에 한해 일시적으로 ':' 로 사용
$ IFS=: read -ra arr <<< "Arch Linux:Ubuntu Linux:Suse Linux"

$ echo "${arr[1]}"             # 정상적으로 필드가 분리 되었다.
Ubuntu Linux

$ echo -n "$IFS" | od -a       # read 명령 사용후 IFS 값 
0000000  sp  ht  nl            # 명령 사용 후 값이 변하지 않았다.

대입한 값은 명령이 실행돼야 적용된다.

다음은 대입 연산이 적용되지 않는데요. 이유는 echo 명령이 시작하기 전에 인수 부분에서 $A $B $C 변수확장이 일어나기 때문입니다.

$ A=1 B=2 C=3
$ A=4 B=5 C=6 echo $A $B $C       # 대입한 값이 적용되지 않는다.
1 2 3

다음과 같이 eval 명령을 사용하면 해결할 수 있습니다.

$ A=1 B=2 C=3

$ A=4 B=5 C=6 eval 'echo $A $B $C'
4 5 6
$ echo $A $B $C                    # 명령 종료 후에는 원래 값으로 복귀
1 2 3                             

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

$ A=4 B=5 C=6 sh -c 'echo $A $B $C'
4 5 6
$ echo $A $B $C
1 2 3

활용하여 array 값을 다룰때도 사용할 수 있습니다.

#!/bin/bash 

r_num=1

set -f
while read -r record; do
    IFS=: eval 'fields=($record)'
    IFS=@ eval 'echo ">>> ${fields[*]}"'
    echo record $r_num has ${#fields[@]} fields
    let r_num++
done < datafile
set +f

uniq, sort -u 차이

파일 내용 중에서 중복되어 존재하는 라인들을 하나의 uniq 한 라인만 남기고 출력하고자 할 때는 uniq 명령을 사용해서는 안되고 sort -u 명령을 사용해야 합니다. 왜냐하면 uniq 명령은 연이어지는 중복 라인에 대해서만 적용되기 때문입니다.

$ cat file
111
222   # 222
333
444   # 444 연이어지는 중복 라인
444
444
555
222   # 222

$ uniq file
111
222   # 222
333
444   # 444 연이어지는 중복 라인만 uniq 라인으로 됨
555
222   # 222 는 그대로 남아 있다.

# -u 옵션은 444 와 같이 연이어지는 중복 라인이 삭제됩니다.
$ uniq -u file
111
222   # 222
333
555
222   # 222 는 남아 있다.
--------------------------------------------

# 파일 내에 존재하는 222, 444 와 같은 모든 중복 라인들이 uniq 라인으로 됩니다.
$ sort -u file
111
222
333
444
555

세로 출력을 가로 출력으로, 가로 출력을 세로 출력으로

세로 출력을 가로 출력으로

$ seq 10
1
2
3
4
5
6
7
8
9
10

$ seq 10 | xargs
1 2 3 4 5 6 7 8 9 10

$ echo $(seq 10)
1 2 3 4 5 6 7 8 9 10

가로 출력을 세로 출력으로

$ echo $(seq 10) | xargs -n1
1
2
3
4
5
6
7
8
9
10

$ echo $(seq 10) | xargs -n2
1 2
3 4
5 6
7 8
9 10

ls *.txtecho *.txt 두 명령을 프롬프트 상에서 실행해 보면 동일하게 가로 출력이 되는데요. 하지만 터미널이 아닌 파이프나 파일로 결과가 전달될 때는 ls 명령은 newline 에 의해 분리되는 세로 출력이 됩니다.

$ ls *.txt 
aaa.txt  bbb.txt  ccc.txt

$ echo *.txt
aaa.txt bbb.txt ccc.txt

$ ls *.txt | wc -l
3

$ echo *.txt | wc -l
1
----------------------------

# nl 은 newline 을 의미
$ ls *.txt | od -a
0000000   a   a   a   .   t   x   t  nl   b   b   b   .   t   x   t  nl
0000020   c   c   c   .   t   x   t  nl
0000030

# sp 는 space 를 의미
$ echo *.txt | od -a
0000000   a   a   a   .   t   x   t  sp   b   b   b   .   t   x   t  sp
0000020   c   c   c   .   t   x   t  nl
0000030

Non-printing 문자가 포함되는지 알아보기

cat 명령을 이용하면 tab 문자 같은 non-printing 문자가 포함되는지 알아볼 수 있습니다.
이외에도 cat 명령은 각 라인의 끝을 표시하거나, 라인 넘버를 붙일 수도 있고 연이어지는 공백 라인을 하나로 만들 수 있습니다.

# tab 은 ^I, \a 는 ^G, \r 은 ^M 으로 표시되는걸 볼 수 있습니다.
$ echo -e "AAA \t BBB \a CCC \r DDD" | cat -t
AAA ^I BBB ^G CCC ^M DDD

$ objdump -d /bin/ls | cat -t 
. . .
. . .
    3658:^I48 83 ec 08          ^Isub    $0x8,%rsp
    365c:^I48 8b 05 7d b9 21 00 ^Imov    0x21b97d(%rip),%rax
    3663:^I48 85 c0             ^Itest   %rax,%rax
    3666:^I74 02                ^Ije     366a <_init@@Base+0x12>
    3668:^Iff d0                ^Icallq  *%rax
    366a:^I48 83 c4 08          ^Iadd    $0x8,%rsp
    366e:^Ic3                   ^Iretq   
. . .
. . .
--------------------------------------------------

# 연이어지는 공백 라인을 하나로 
$ echo -e "AAA\nBBB\nCCC\n\n\n\n\n\nDDD" | cat -s
AAA
BBB
CCC

DDD

sudo 명령 사용 시 미리 패스워드 인증하기

스크립트 내에서 sudo 명령을 사용할 경우 시작 전에 미리 패스워드 인증을 하는 것이 좋습니다. sudo 명령은 처음에 한번 패스워드 인증을 하면 결과를 cache 하므로 다음부터는 인증을 하지 않습니다.

#!/bin/sh

# 스크립트 처음에 -v 옵션으로 미리 패스워드 인증을 합니다.
sudo -v -p "\
root privilege required.
[sudo] password for $USER: " || exit 1

.....
.....

# 이후에 사용되는 sudo 명령에서는 패스워드 인증을 하지 않게 됩니다.
sudo id

특정 디렉토리, 파일 모니터링하기

inotify-tools 를 이용하면 특정 디렉토리( 또는 이하 디렉토리 ), 파일을 event 별로 모니터링할 수 있습니다.

# 먼저 inotify-tools 패키지 설치
# inotifywait 와 inotifywatch 두 개의 실행파일이 생성됩니다.
$ sudo apt install inotify-tools

# 1. /tmp 디렉토리 이하를 모니터링하기 위해 inotifywait 명령 실행
# 2. 테스트를 위해 firefox 브라우저 실행
$ inotifywait -r -m -e create,delete,access,modify /tmp
Setting up watches.  Beware: since -r was given, this may take a while!
Watches established.
/tmp/ CREATE,ISDIR firefox_mug896
/tmp/ ACCESS,ISDIR firefox_mug896
/tmp/firefox_mug896/ CREATE lock
/tmp/firefox_mug896/ DELETE lock
/tmp/firefox_mug896/ DELETE .parentlock
/tmp/ DELETE,ISDIR firefox_mug896
^C

Quiz

cat 명령의 반대는 무엇일까요?

알파벳을 거꾸로 하면 됩니다.

$ seq 5 | cat
1
2
3
4
5

$ seq 5 | tac
5
4
3
2
1