bash 스크립트는 자주 사용하면서도 기억이 잘 안나는 부분이 많아서 정리해둔다.
까먹은 명령어나 기능을 바로바로 찾아보기 위해 만든 문서이다.
목차
bash
스크립트 실행법
chmod
를 이용하여 파일에 실행권한을 주고
1
2
|
chmod +x <filename>
chmod 755 <filename>
|
파일의 최상단에 #!/bin/bash
를 추가한다. 리눅스 스크립트의 경우 파일의 최상단에 #!
로 시작하는 줄이 있을 경우 해당 프로그램으로 스크립트를 동작시킨다.
또는 /bin/bash <filename>
으로 실행할 수 있다.
주석 처리
#
을 이용하여 주석 처리한다.
변수
변수는 변수명=값
형식으로 생성한다.
중요 : 이 때 =
앞뒤로 공백을 넣으면 안된다.
1
2
3
|
var1=10
var2="string"
var3=/usr/bin/python3
|
변수 생성시에는 =
앞뒤로 공백을 넣으면 syntax 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
|
변수 (문자열)의 길이를 획득하고 싶어요
- wc -L 사용
1
2
|
var1="apple is sweet"
echo "$var1" | wc -L
|
-
expr length
사용
1
2
|
var1="apple is sweet"
expr length "$var1"
|
-
awk {print length($0)
사용
1
2
|
var1="apple is sweet"
echo "$var1" | awk '{print length($0)}'
|
-
#
사용
1
2
|
var1="apple is sweet"
echo "${#var1}"
|
응용 : 문자열의 마지막 문자를 빼고 출력하고 싶어요
1
2
3
|
var1="apple is sweet"
echo "${var1::$(expr ${#var1} - 1)}"
# apple is swee
|
변수 (문자열)의 값을 치환하고 싶어요 (문자열 치환 - replace)
-
tr
을 사용한다.
1
2
|
echo "apple" | tr "p" "q"
# aqqle
|
-
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
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 |
변수가 선언되어 있으면 참 |
|
문자열 포함 여부를 확인하고 싶어요
-
if [[ "$A" =~ "$B" ]]
를 사용한다. $B
가 $A
에 포함되어 있으면 참이다.
1
2
3
|
if [[ "apple is sweet" =~ "sw" ]]; then
# true
fi
|
-
if grep -q "$A" <<< "$B"
를 사용한다. 1번과 반대로 $A
가 $B
에 포함되어 있으면 참이다.
1
2
3
|
if grep -q "ee" <<<"apple is sweet"; then
# true
fi
|
함수 (function), 알리아스 (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
|
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 를 제대로 사용할 수 있으면 정말 유용하다.
특정 라인 출력
- 파일이 아닌 cat, echo 내용도 출력할 수 있다.
1
2
3
|
echo "${content}" | sed -n "3p"
cat file.txt | sed -n "1,3p"
|
특정 라인을 삭제
특정 라인을 변경
사용방법
1
2
3
4
5
|
# <라인>의 <찾을문자열>을 <바꿀문자열>로 변경
sed -i "<라인>s/찾을문자열/바꿀문자열/" <파일명>
# <라인1>~<라인2>의 <찾을문자열>을 <바꿀문자열>로 변경
sed -i "<라인1>,<라인2>s/찾을문자열/바꿀문자열/" <파일명>
|
1
|
sed -i "3s/.*/asdf asdf/" file.txt
|
- 2~3번째 라인 전체를 asdf adsf로 변경
1
|
sed -i "2,3s/.*/asdf asdf/" file.txt
|
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
|
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
|
바꿀 대상에 정규식을 사용할 수도 있다.
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 $<출력할 컬럼>}'
|
예제
아래 방식대로 해도 되지만
1
2
3
|
wc -l file.txt | awk '{print $1}'
# 120 file.txt 내용을 공백으로 구분한 뒤 첫번째 컬럼을 출력하는 것이니
# 120 이 출력된다.
|
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)
|