Post

bash manual

bash 스크립트는 자주 사용하면서도 기억이 잘 안나는 부분이 많아서 정리해둔다.

까먹은 명령어나 기능을 바로바로 찾아보기 위해 만든 문서이다.

목차


bash

스크립트 실행법

chmod를 이용하여 파일에 실행권한을 주고

1
2
chmod +x <filename>
chmod 755 <filename>

파일의 최상단에 #!/bin/bash를 추가한다. 리눅스 스크립트의 경우 파일의 최상단에 #!로 시작하는 줄이 있을 경우 해당 프로그램으로 스크립트를 동작시킨다.

1
#!/bin/bash

또는 /bin/bash <filename> 으로 실행할 수 있다.

주석 처리

#을 이용하여 주석 처리한다.

1
# comment

변수

변수는 변수명=값 형식으로 생성한다.
중요 : 이 때 = 앞뒤로 공백을 넣으면 안된다.

1
2
3
var1=10
var2="string"
var3=/usr/bin/python3

변수 생성시에는 = 앞뒤로 공백을 넣으면 syntax error

1
var4 = 10 # error

명령어 출력 결과를 변수에 저장하고 싶어요

명령어를 ` `` ` 또는 $()로 감싸 변수로 저장하면 된다.

1
2
var1=`pwd`
var2=$(ls -al)

변수를 사용/출력하고 싶어요

변수를 사용할 때에는 $를 붙여 사용한다.
$varname 또는 중괄호를 붙여 ${varname}으로 사용할 수 있다.

변수를 출력할 때에는 echo와 함께 사용한다. 변수를 그냥 사용할 때와 큰따옴표를 붙여 사용할 때의 출력결과에는 차이가 존재한다.

  • 그냥 사용 : 변수의 값이 출력된다. 이 때 여러개의 공백과 줄바꿈은 하나의 공백으로 출력된다.
  • ' ' 작은따옴표로 감싸 사용 : $ 표시를 변수로 인식하지 않고 string 그대로 출력한다.
  • " " 큰따옴표로 감싸 사용 : 변수의 값이 출력된다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
var1="apple       is sweet
and delicious"
echo $var1
# apple is sweet and delicious
echo ${var1}
# apple is sweet and delicious
echo "var1 : $var1"
# var1 : apple       is sweet
# and delicious
echo "var1:${var1}"
# var1 : apple       is sweet
# and delicious
echo 'var1 : $var1'
# var1 : $var1

변수의 일부만 출력하고 싶어요

${varname:offset:length} 형식으로 사용한다.

1
2
3
4
5
var1="apple is sweet"
echo "${var1:2}"
# ple is sweet
echo "${var1:1:2}"
# pp

변수 (문자열)의 길이를 획득하고 싶어요

  1. wc -L 사용
    1
    2
    
    var1="apple is sweet"
    echo "$var1" | wc -L
    
  2. expr length 사용
    1
    2
    
    var1="apple is sweet"
    expr length "$var1"
    
  3. awk {print length($0) 사용
    1
    2
    
    var1="apple is sweet"
    echo "$var1" | awk '{print length($0)}'
    
  4. # 사용
    1
    2
    
    var1="apple is sweet"
    echo "${#var1}"
    

응용 : 문자열의 마지막 문자를 빼고 출력하고 싶어요

1
2
3
var1="apple is sweet"
echo "${var1::$(expr ${#var1} - 1)}"
# apple is swee

변수 (문자열)의 값을 치환하고 싶어요 (문자열 치환 - replace)

  1. tr을 사용한다.
    • tr은 문자 단위로 치환한다
1
2
echo "apple" | tr "p" "q"
# aqqle
  1. sed를 사용한다.
    • sed는 문자열 단위로 치환한다.
1
2
3
4
echo "apple apple is sweet" | sed -e "s/apple/orange/"
# orange apple is sweet
echo "apple apple is sweet" | sed -e "s/apple/orange/g"
# orange orange is sweet

사칙연산을 진행하고 싶어요

expr을 사용한다.
주의 : 실수 연산은 지원하지 않는다. (오직 정수 연산만 가능)

1
expr 1 + 1

변수에 값을 더하고 싶어요

(( ))을 사용한다.
주의 : 실수 연산은 지원하지 않는다. (오직 정수 연산만 가능)

1
2
3
4
5
var=0
((var++))
# var = 1
((var+=2))
# var = 3

응용하면 다음과 같이 사용할 수 있다.
test.log의 마지막줄 - 1 번째 라인을 출력하고 싶을 때

1
sed -n "$(expr $(wc -l test.log | awk '{print $1}') - 1)p" test.log

변수 사용 시 주의사항!

변수에 빈 값이 들어가거나 명령어 결과값이 존재하지 않는 경우에도 배쉬 스크립트는 그대로 동작한다.
따라서 변수의 내용을 검증하지 않고 사용 시 대참사가 발생할 수도 있다.

아래와 같은 코드에서 test.py 파일이 존재하지 않는다면 rm -rf ./ 로 실행되어 현재 디렉토리가 삭제될 위험이 존재한다.

1
2
var1=`test -f ./test.py && echo "test.py"
rm -rf ./$var1

IF문을 사용하고 싶어요

  • if 조건절 구문은 [ ] [[ ]] 둘 중 하나를 사용할 수 있다.
  • [ ] : POSIX 표준
  • [[ ]] : bash 표준

다음과 같은 형식으로 사용할 수 있다.

1
2
3
4
5
6
7
if [ 조건 ]; then # 대괄호는 [ ] 또는 [[ ]] 사용이 가능하다.
    조건이 참일 때 실행할 명령어
elif [ 조건 ]; then
    조건이 참일 때 실행할 명령어
else
    조건이 거짓일 때 실행할 명령어
fi
1
2
3
4
5
6
7
8
9
10
11
12
13
# if () {
# } 표기법
if [ 조건 ]; then
  # code
fi

# if ()
# {
# } 표기법
if [ 조건 ]
then
  # code
fi
연산자 의미 비고
-eq == (EQual)  
-ne != (NEgative)  
-gt > (Greater Than)  
-lt < (Less Than)  
-ge >= (Greater than Equal)  
-le <= (Less than Equal)  
== == zsh 호환 X
!= !=  
-e 파일/디렉토리 존재 여부 해당 내용이 파일인지 디렉토리인지 구분하지 않음
-f 파일 존재 여부  
-d 디렉토리 존재 여부  
-r 읽기 가능한 파일 여부  
-w 쓰기 가능한 파일 여부  
-x 실행 가능한 파일 여부  
-z 문자열 길이가 0이면 참  
-n 문자열 길이가 0보다 크면 참  
-v 변수가 선언되어 있으면 참  

문자열 포함 여부를 확인하고 싶어요

  1. if [[ "$A" =~ "$B" ]] 를 사용한다. $B$A에 포함되어 있으면 참이다.
1
2
3
if [[ "apple is sweet" =~ "sw" ]]; then
# true
fi
  1. if grep -q "$A" <<< "$B" 를 사용한다. 1번과 반대로 $A$B에 포함되어 있으면 참이다.
1
2
3
if grep -q "ee" <<<"apple is sweet"; then
# true
fi

함수 (function), 알리아스 (alias)가 정의되어 있는지 확인하고 싶어요

  • alias 정의 여부 확인
1
2
3
if (alias testalias > /dev/null 2>&1); then
  unalias fzf
fi
  • 함수 정의 여부 확인
1
2
3
if (declare -f -F "testfunc" > /dev/null); then
  unset -f fzf
fi

반복문

for 문

foreach 형식

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
for i in 1 2 3 4 5; do
  echo $i
done
# 1 ~ 5 출력

for i in {1..5}; do
  echo $i
done
# 1 ~ 5 출력

for i in {0..10..2}; do
  echo $i
done
# 0 2 4 6 8 10 출력

for i in {5..0..1}; do
  echo $i
done
# 5 4 3 2 1 0 출력

for i in $(seq 0 1 9); do
  echo $i
done
# 0 ~ 9 출력

arr=("str1" "str2"
"str3" "str4" "str5")

for i in "${arr[@]}"; do
  echo $i
done

# str1 str2 str3 str4 str5 출력

for 형식

1
2
3
4
for (( i=0; i<10; i++ )); do
  echo $i
done
# 0 ~ 9 출력

while 문

1
2
3
4
5
6
var=0
while [ $var -lt 3 ]; do
  echo "test"
  ((var++))
done
# test 3번 출력

무한 while 루프를 사용하고 싶어요

while [ : ] 를 사용한다.

1
2
3
4
while [ : ]; do
  echo "test"
done
# test가 무한으로 출력된다.

반복문을 빠져나오고 싶어요

break 를 사용한다.

무한루프 이지만 var가 3이 되면 if문을 타고 break가 걸려 반복문을 빠져나온다.

1
2
3
4
5
6
7
8
9
var=0
while [ : ]; do
  if [ $var -ge 3 ]; then
    break
  fi
  echo "test"
  ((var++))
done
# test 3번 출력

배열/리스트

() 를 사용한다.

선언

  • (값1 값2 값3 값4 값5 ...)

요소는 공백으로 구분한다.

1
2
3
var1=(1 2 3 4 5)
echo ${var1[@]}
# 1 2 3 4 5

배열의 전체 요소를 출력하고 싶어요

${varname[@]} 를 이용한다.

1
2
var1=(1 2 3 4 5)
echo ${var1[@]}

특정 요소를 출력하고 싶어요

${varname[index]} 를 이용한다.
일반적으로 변수 출력하듯이 사용하면 첫 번째 인덱스의 값만 리턴된다.

1
2
3
4
5
var1=(1 2 3 4 5)
echo ${var1[1]}
# 2
echo ${var1}
# 1

번외 : bash와 zsh는 인덱싱하는 위치가 다르다.
bash는 0부터 시작하지만 zsh는 1부터 시작한다. (zsh에서 setopt ksharrays 옵션을 사용하면 0부터 시작한다.)
이 때 bash와 zsh 모두 호환성을 유지하고싶다면 아래와 같이 사용하면 된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
testarr=(
    "asdf"
    "qwer qwer"
    "zxcv zxcv zxcv"
    "1234 1234 1234 1234"
)

# 마지막 요소 출력
echo "${testarr[@]:${#testarr[@]}-1:1}" # 1234 1234 1234 1234
# 첫(0) 번째 요소 출력
echo "${testarr[@]:0:1}" # asdf
# 두(1) 번째 요소 출력
echo "${testarr[@]:1:1}" # qwer qwer

배열의 특정 요소부터 특정 요소까지 출력하고 싶어요

문자열의 특정 요소를 출력하는 것과 동일하게 사용하면 된다.

1
2
3
4
5
6
7
8
9
10
11
12
testarr=(
    "asdf"
    "qwer qwer"
    "zxcv zxcv zxcv"
    "1234 1234 1234 1234"
)

# 첫(0) 번째 요소를 제외한 나머지 요소 출력
echo "${testarr[@]:1:${#testarr[@]}}"

# 마지막 요소를 제외한 나머지 요소 출력
echo "${testarr[@]:0:${#testarr[@]}-1}"

배열의 길이 (size) 를 확인하고 싶어요

${#varname[@]} 를 이용한다.

1
2
3
var1=(1 2 3 4 5)
echo ${#var1[@]}
# 5

배열에 요소를 추가하고 싶어요

+= 을 사용한다. 추가 시 ( ) 로 묶어주지 않으면 첫 번째 인덱스의 값에 추가됨을 유의하자.

1
2
3
4
5
6
7
8
9
var1=(1 2 3 4 5)
var1+=(6)
echo ${var1[@]}
# 1 2 3 4 5 6

# ERR
var1+=1
echo ${var1[@]}
# 11 2 3 4 5 6

배열에 요소를 제거하고 싶어요

  • / 를 사용한다.
    1
    2
    3
    4
    
    var1=(1 2 3 4 5)
    var1=(${var1[@]/2})
    echo ${var1[@]}
    # 1 3 4 5
    
  • unset을 사용한다.
    1
    2
    3
    4
    
    var1=(1 2 3 4 5)
    unset var1[1]
    echo ${var1[@]}
    # 1 3 4 5
    

주의사항
/는 요소를 제거하는 것이 아닌 리스트 내 매칭되는 string을 제거하는 것에 유념하자.
아래 예시를 보면 var[2]는 32 이지만 2 제거 후 3이 출력되는 것을 볼 수 있다.

1
2
3
4
var1=(1 2 32 4 5)
var1=(${var1[@]/2})
echo ${var1[@]}
# 1 3 4 5

배열을 순회하고 싶어요

for를 사용한다. foreach 형식으로 순회하여도 되고 for로 인덱스를 직접접근 할 수도 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var1=(1 2 3)

for i in ${var1[@]}; do
    echo $i
done
# 1
# 2
# 3

for (( i=0; i<${#var1[@]}; i++ )); do
    echo ${var1[$i]}
done
# 1
# 2
# 3

문자열을 리스트로 나누고 싶어요

’ ‘ 공백을 기준으로 문자열을 리스트로 나누고 싶을 때

그냥 변수를 ( ) 로 감싸주면 끝

1
2
var1="qwer asdf zxcv"
var2=($var1)

‘\n’ 개행 등 기타 구분자를 기준으로 문자열을 리스트로 나누고 싶을 때

IFS 를 구분자로 지정하여 ( ) 로 감싸주면 끝

1
2
3
4
5
6
7
8
var1="qwer
asdf
zxcv"

SAVEIFS=$IFS  # 기존 IFS 저장
IFS=$'\n'     # IFS 재설정
var2=($var1)  # 리스트로 변환
IFS=$SAVEIFS  # IFS 복원

함수

function 함수명() { } 또는 함수명() { } 로 함수를 정의한다.

1
2
3
4
5
6
7
function func1() {
    echo "func1"
}

func2() {
    echo "func2"
}

정의한 함수를 사용하고 싶어요

함수명으로 사용하면 된다.

1
2
3
4
func() {
    echo "func"
}
func

argument까지 같이 전달하고 싶을 때에는 함수명 뒤에 argument를 같이 입력해주면 된다.
argument는 공백으로 구분된다.
따라서 공백을 포함하여 argument로 전달하고 싶을 때에는 ' ' 작은따옴표나 " " 큰따옴표로 감싸주면 된다.

1
2
3
4
5
func() {
    # ... code ...
}

func "qwer" "asdf zxcv"

함수에 로컬 변수를 사용하고 싶어요

아래 예제 코드와 같이 함수 내부에서 변수를 생성하더라도 함수 밖에서 변수를 사용할 수 있다.

1
2
3
4
5
6
7
8
9
10
func() {
    var1="test"
    echo "var1 : ${var1}"
}
func
echo "var : ${var1}"

# output
# var1 : test
# var : test

이를 방지하기 위해서 local 키워드를 사용한다.

1
2
3
4
5
6
7
8
9
func() {
    local var1="test"
    echo "var1 : ${var1}"
}
func
echo "var : ${var1}"
# output
# var1 : test
# var :

함수의 argument를 사용하고 싶어요

$1, $2, … $n 으로 사용할 수 있다.

1
2
3
4
5
6
7
8
func() {
    echo "first arg: ${1}"
    echo "second arg: ${2}"
}
func "qwer" "asdf zxcv"
# output
# first arg: qwer
# second arg: asdf zxcv

함수에 전달된 argument의 갯수를 알고 싶어요

$# 으로 사용할 수 있다.

1
2
3
4
5
6
func() {
    echo "argument count: ${#}"
}
func "qwer" "asdf zxcv" "1234"
# output
# argument count: 3

함수에 전달된 argument를 전부 출력하고 싶어요

$@ 으로 사용할 수 있다.

1
2
3
4
5
6
func() {
    echo "argument: ${@}"
}
func "1234" "qwer" "asdf zxcv"
# output
# argument: 1234 qwer asdf zxcv

함수에 전달된 argument를 일부 출력하고 싶어요

${@:<시작 index>: <갯수>} 로 사용할 수 있다.
<시작 index> 는 1부터 시작한다.
<갯수> 는 생략할 수 있다. 생략하면 시작 index부터 끝까지 출력한다.

1
2
3
4
5
6
7
8
9
10
11
12
func() {
    echo "$1"
    echo "${@}"
    echo "${@:2:3}"
}

func aa "sss sss" bbb ccc ddd

# output
# aa
# aa sss sss bbb ccc ddd
# sss sss bbb ccc

선언한 함수를 삭제하고 싶어요

unset 으로 삭제할 수 있다.

1
2
3
4
5
func() {
  echo "func"
}
func
unset func

함수를 도중에 종료하고 싶어요

return 으로 종료할 수 있다.
return을 사용한다고 해서 함수의 값을 반환할 수 있는 것은 아니다.
return 후 0~255 사이의 값을 전달하여 함수의 종료코드를 전달할 수 있다.

1
2
3
4
5
6
7
8
func() {
  if [ $# -eq 0 ]; then
    echo "argument is empty"
    return 1
  fi
}
func
echo $?   # 1

함수의 값을 반환하고 싶어요

방법이 없다..
편법을 쓰자면 echo 를 이용하여 값을 반환하는 척 할 수는 있다.

1
2
3
4
5
6
7
8
9
func() {
  if [[ "$@" = "qwer" ]]; then
    echo "qwerty"
    return
  fi
  echo "test"
}
var1=$(func "qwer")
echo "$var1"    # qwerty

번외 sed 사용법

sed 를 제대로 사용할 수 있으면 정말 유용하다.

특정 라인 출력

  • file.txt의 3번째 라인을 출력
1
sed -n '3p' file.txt
  • file.txt의 1~3 라인을 출력
1
sed -n '1,3p' file.txt
  • 파일이 아닌 cat, echo 내용도 출력할 수 있다.
1
2
3
echo "${content}" | sed -n "3p"

cat file.txt | sed -n "1,3p"

특정 라인을 삭제

  • file.txt의 3번째 라인을 삭제
1
sed -i '3d' file.txt
  • file.txt의 1~3 라인을 삭제
1
sed -i '1,3d' file.txt

특정 라인을 변경

사용방법

1
2
3
4
5
# <라인>의 <찾을문자열>을 <바꿀문자열>로 변경
sed -i "<라인>s/찾을문자열/바꿀문자열/" <파일명>

# <라인1>~<라인2>의 <찾을문자열>을 <바꿀문자열>로 변경
sed -i "<라인1>,<라인2>s/찾을문자열/바꿀문자열/" <파일명>
  • 3번째 라인 전체를 asdf asdf로 변경
1
sed -i "3s/.*/asdf asdf/" file.txt
  • 2~3번째 라인 전체를 asdf adsf로 변경
1
sed -i "2,3s/.*/asdf asdf/" file.txt
  • 2번째 라인의 asdf를 hjkl로 변경
1
sed -i "2s/asdf/hjkl/" file.txt

특정 단어를 치환

사용방법

1
2
3
4
5
6
7
8
9
10
11
12
13
sed -i "s/바꿀대상/바꿀문자열/옵션" <변경 파일>
# 옵션
# g - 모든 대상을 변경 (global)
# i - 대소문자를 구분하지 않음 (ignore case)

# 첫 번째 대상만 변경
sed -i "s/바꿀대상/바꿀문자열/" <변경 파일>

# 매칭되는 모든 대상 변경 (global 옵션)
sed -i "s/바꿀대상/바꿀문자열/g" <변경 파일>

# 대소문자를 구분하지 않고 매칭되는 모든 대상을 변경 (global ignore case 옵션)
sed -i "s/바꿀대상/바꿀문자열/gi" <변경 파일>
  • file.txt의 qwerty를 asdf로 치환
1
sed -i "s/qwerty/asdf/" file.txt
  • file.txt의 zz를 oo로 치환
1
sed -i "s/zz/oo/g" file.txt
  • 변수를 치환할 수도 있다.
1
2
3
4
5
var1="apple apple is sweet"
echo "$var1" | sed -e "s/apple/orange/"
# orange apple is sweet
echo "$var1" | sed -e "s/apple/orange/g"
# orange orange is sweet

바꿀 대상에 정규식을 사용할 수도 있다.

  • file.txt의 숫자를 모두 삭제
1
sed -i "s/[0-9]//g" file.txt
  • 대소문자 구분하지 않고 asdf 를 oooo로 모두치환
1
sed -i "s/asdf/oooo/gi" file.txt

구분자는 ‘/’가 아닌 다른 기호가 될 수도 있다.

  • 1번째 라인의 #!/bin/bash#!/usr/bin/python3로 변경
1
sed -i "1s@#\!/bin/bash@#\!/usr/bin/python3@" file.txt
  • file.txt의 /etc/crontab/etc/cron.d/test로 변경
1
sed -i "s#/etc/crontab#/etc/cron.d/test#g" file.txt

sed로 삭제

사용방법

1
sed -i "/찾을문자열/d" <변경 파일>
  • file.txt의 숫자가 존재하는 라인을 모두 삭제
1
sed -i "/[0-9]/d" file.txt

특정 조건 위/아래에 내용을 추가

사용방법

1
2
3
4
5
6
7
8
9
10
# a\ - 아래에 내용 추가
# i\ - 위에 내용 추가

# 특정 라인 밑에 내용을 추가
sed -i '<라인>a\<추가할 내용>' <변경 파일>
# 특정 라인 위에 내용을 추가
sed -i '<라인>i\<추가할 내용>' <변경 파일>

# 특정 단어 밑에 내용을 추가
sed -i -e '/<조건>/a\<추가할 내용>' <변경 파일>
1
2
3
4
* 3번째 라인 뒤에 asdf asdf를 추가

```bash
sed -i "3a\asdf asdf" file.txt
  • asdf가 포함되는 라인 밑에 uiop 문자열 추가
1
sed -i -e "/asdf/a\uiop" file.txt

번외 awk 사용법

bash 스크립트는 사실 sed와 awk만 잘 사용하면 거의 대부분의 작업을 할 수 있다고 생각한다.

사용방법

1
2
3
# 구분자 없을 시 ' ' (공백)으로 구분
awk -F '<구분자>' '{print $<출력할 컬럼>}' <파일>
<출력내용> | awk -F '<구분자>' '{print $<출력할 컬럼>}'

예제

  • file.txt의 줄 수를 출력

아래 방식대로 해도 되지만

1
cat file.txt | wc -l
  • awk로 잘라도 된다.
1
2
3
wc -l file.txt | awk '{print $1}'
# 120 file.txt 내용을 공백으로 구분한 뒤 첫번째 컬럼을 출력하는 것이니
# 120 이 출력된다.
  • file.txt의 사이즈 출력
1
2
3
4
5
6
wc -c file.txt | awk '{print $1}'
# 90 file.txt 내용을 공백으로 구분한 뒤 첫번째 컬럼 출력 (90)

ls -al file.txt | awk '{print $5}'
# -rw-r--r-- 1 root root 90 Dec 21 11:20 file.txt
# 아래 라인에서 공백으로 구분한 뒤 5번째 컬럼 출력 (90)

번외 (Python)

  • python -c 를 이용하여 python 코드를 실행할 수 있다.
1
var1=$(python3 -c "print(1.3 + 2.7)")

기타 유용한 예제

리눅스 메뉴얼 문서도 참고하면 좋다.

쉘 스크립트 실행 파일 path 찾기

pwd로 가져오면 쉘 스크립트를 실행했을 때의 경로에 따라 위치가 달라질 수 있어 쉘 스크립트 실행 파일의 경로를 찾아야 할 때가 있다. test.sh 쉘 스크립트가 /etc/에 존재한다고 가정할 때

1
2
3
4
5
6
7
8
cd /
/etc/test.sh # pwd 시 / 출력

cd /usr
/etc/test.sh # pwd 시 /usr 출력

cd /etc
./test.sh # pwd 시 /etc 출력

쉘 스크립트의 실행하는 위치에 따라 pwd로 가져온 경로가 달라진다. 즉, 아래 예제와 같을 경우 /etc에서 실행하지 않으면 confpath.conf 를 찾을 수 없다.

1
2
curpath=`pwd`
confpath=$($curpath/confpath.conf)

따라서 아래 스크립트 를 참고하여 쉘 스크립트가 위치해 있는 경로를 구해 confpath.conf를 찾을 수 있도록 한다.

1
2
curpath=$(dirname $(realpath $0))
cd $curpath

한줄 코딩

1
cd $(dirname $(realpath $0))
1
confpath=$($curpath/confpath.conf)
This post is licensed under CC BY 4.0 by the author.