bash 스크립트는 자주 사용하면서도 기억이 잘 안나는 부분이 많아서 정리해둔다.
까먹은 명령어나 기능을 바로바로 찾아보기 위해 만든 문서이다.
전체 문서는 개인블로그 링크에서 확인 가능하다.
https://dev.iasdf.com/wiki/manual/bash
chmod를 이용하여 파일에 실행권한을 주고
chmod +x <filename>
chmod 755 <filename>
파일의 최상단에 #!/bin/bash를 추가한다.
리눅스 스크립트의 경우 파일의 최상단에 #!로 시작하는 줄이 있을 경우 해당 프로그램으로 스크립트를 동작시킨다.
#!/bin/bash
또는 /bin/bash <filename> 으로 실행할 수 있다.
#을 이용하여 주석 처리한다.
# comment
변수는 변수명=값 형식으로 생성한다.
중요 : 이 때 = 앞뒤로 공백을 넣으면 안된다.
var1=10
var2="string"
var3=/usr/bin/python3
변수 생성시에는 = 앞뒤로 공백을 넣으면 syntax error
var4 = 10 # error
명령어를 `` 또는 $()로 감싸 변수로 저장하면 된다.
var1=`pwd`
var2=$(ls -al)
변수를 사용할 때에는 $를 붙여 사용한다.
$varname 또는 중괄호를 붙여 ${varname}으로 사용할 수 있다.
변수를 출력할 때에는 echo와 함께 사용한다.
변수를 그냥 사용할 때와 큰따옴표를 붙여 사용할 때의 출력결과에는 차이가 존재한다.
' ' 작은따옴표로 감싸 사용 : $ 표시를 변수로 인식하지 않고 string 그대로 출력한다." " 큰따옴표로 감싸 사용 : 변수의 값이 출력된다.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} 형식으로 사용한다.
var1="apple is sweet"
echo "${var1:2}"
# ple is sweet
echo "${var1:1:2}"
# pp
var1="apple is sweet"
echo "$var1" | wc -L
expr length 사용var1="apple is sweet"
expr length "$var1"
awk {print length($0) 사용var1="apple is sweet"
echo "$var1" | awk '{print length($0)}'
# 사용var1="apple is sweet"
echo "${#var1}"
var1="apple is sweet"
echo "${var1::$(expr ${#var1} - 1)}"
# apple is swee
tr을 사용한다.echo "apple" | tr "p" "q"
# aqqle
sed를 사용한다.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을 사용한다.
주의 : 실수 연산은 지원하지 않는다. (오직 정수 연산만 가능)
expr 1 + 1
(( ))을 사용한다.
주의 : 실수 연산은 지원하지 않는다. (오직 정수 연산만 가능)
var=0
((var++))
# var = 1
((var+=2))
# var = 3
응용하면 다음과 같이 사용할 수 있다.
test.log의 마지막줄 - 1 번째 라인을 출력하고 싶을 때
sed -n "$(expr $(wc -l test.log | awk '{print $1}') - 1)p" test.log
변수에 빈 값이 들어가거나 명령어 결과값이 존재하지 않는 경우에도 배쉬 스크립트는 그대로 동작한다.
따라서 변수의 내용을 검증하지 않고 사용 시 대참사가 발생할 수도 있다.
아래와 같은 코드에서 test.py 파일이 존재하지 않는다면 rm -rf ./ 로 실행되어 현재 디렉토리가 삭제될 위험이 존재한다.
var1=`test -f ./test.py && echo "test.py"
rm -rf ./$var1
[ ] [[ ]] 둘 중 하나를 사용할 수 있다.[ ] : POSIX 표준[[ ]] : bash 표준다음과 같은 형식으로 사용할 수 있다.
if [ 조건 ]; then # 대괄호는 [ ] 또는 [[ ]] 사용이 가능하다.
조건이 참일 때 실행할 명령어
elif [ 조건 ]; then
조건이 참일 때 실행할 명령어
else
조건이 거짓일 때 실행할 명령어
fi
# 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에 포함되어 있으면 참이다.if [[ "apple is sweet" =~ "sw" ]]; then
# true
fi
if grep -q "$A" <<< "$B" 를 사용한다.$A가 $B에 포함되어 있으면 참이다.if grep -q "ee" <<<"apple is sweet"; then
# true
fi
if (alias testalias > /dev/null 2>&1); then
unalias fzf
fi
if (declare -f -F "testfunc" > /dev/null); then
unset -f fzf
fi
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 (( i=0; i<10; i++ )); do
echo $i
done
# 0 ~ 9 출력
var=0
while [ $var -lt 3 ]; do
echo "test"
((var++))
done
# test 3번 출력
while [ : ] 를 사용한다.
while [ : ]; do
echo "test"
done
# test가 무한으로 출력된다.
break 를 사용한다.
무한루프 이지만 var가 3이 되면 if문을 타고 break가 걸려 반복문을 빠져나온다.
var=0
while [ : ]; do
if [ $var -ge 3 ]; then
break
fi
echo "test"
((var++))
done
# test 3번 출력
() 를 사용한다.
(값1 값2 값3 값4 값5 ...)요소는 공백으로 구분한다.
var1=(1 2 3 4 5)
echo ${var1[@]}
# 1 2 3 4 5
${varname[@]} 를 이용한다.
var1=(1 2 3 4 5)
echo ${var1[@]}
${varname[index]} 를 이용한다.
일반적으로 변수 출력하듯이 사용하면 첫 번째 인덱스의 값만 리턴된다.
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 모두 호환성을 유지하고싶다면 아래와 같이 사용하면 된다.
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
문자열의 특정 요소를 출력하는 것과 동일하게 사용하면 된다.
testarr=(
"asdf"
"qwer qwer"
"zxcv zxcv zxcv"
"1234 1234 1234 1234"
)
# 첫(0) 번째 요소를 제외한 나머지 요소 출력
echo "${testarr[@]:1:${#testarr[@]}}"
# 마지막 요소를 제외한 나머지 요소 출력
echo "${testarr[@]:0:${#testarr[@]}-1}"
${#varname[@]} 를 이용한다.
var1=(1 2 3 4 5)
echo ${#var1[@]}
# 5
+= 을 사용한다.
추가 시 ( ) 로 묶어주지 않으면 첫 번째 인덱스의 값에 추가됨을 유의하자.
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
/ 를 사용한다.var1=(1 2 3 4 5)
var1=(${var1[@]/2})
echo ${var1[@]}
# 1 3 4 5
unset을 사용한다.var1=(1 2 3 4 5)
unset var1[1]
echo ${var1[@]}
# 1 3 4 5
주의사항
/는 요소를 제거하는 것이 아닌 리스트 내 매칭되는 string을 제거하는 것에 유념하자.
아래 예시를 보면 var[2]는 32 이지만 2 제거 후 3이 출력되는 것을 볼 수 있다.
var1=(1 2 32 4 5)
var1=(${var1[@]/2})
echo ${var1[@]}
# 1 3 4 5
for를 사용한다.
foreach 형식으로 순회하여도 되고 for로 인덱스를 직접접근 할 수도 있다.
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
그냥 변수를 ( ) 로 감싸주면 끝
var1="qwer asdf zxcv"
var2=($var1)
IFS 를 구분자로 지정하여 ( ) 로 감싸주면 끝
var1="qwer
asdf
zxcv"
SAVEIFS=$IFS # 기존 IFS 저장
IFS=$'\n' # IFS 재설정
var2=($var1) # 리스트로 변환
IFS=$SAVEIFS # IFS 복원
function 함수명() { } 또는 함수명() { } 로 함수를 정의한다.
function func1() {
echo "func1"
}
func2() {
echo "func2"
}
함수명으로 사용하면 된다.
func() {
echo "func"
}
func
argument까지 같이 전달하고 싶을 때에는 함수명 뒤에 argument를 같이 입력해주면 된다.
argument는 공백으로 구분된다.
따라서 공백을 포함하여 argument로 전달하고 싶을 때에는 ' ' 작은따옴표나 " " 큰따옴표로 감싸주면 된다.
func() {
# ... code ...
}
func "qwer" "asdf zxcv"
아래 예제 코드와 같이 함수 내부에서 변수를 생성하더라도 함수 밖에서 변수를 사용할 수 있다.
func() {
var1="test"
echo "var1 : ${var1}"
}
func
echo "var : ${var1}"
# output
# var1 : test
# var : test
이를 방지하기 위해서 local 키워드를 사용한다.
func() {
local var1="test"
echo "var1 : ${var1}"
}
func
echo "var : ${var1}"
# output
# var1 : test
# var :
$1, $2, ... $n 으로 사용할 수 있다.
func() {
echo "first arg: ${1}"
echo "second arg: ${2}"
}
func "qwer" "asdf zxcv"
# output
# first arg: qwer
# second arg: asdf zxcv
$# 으로 사용할 수 있다.
func() {
echo "argument count: ${#}"
}
func "qwer" "asdf zxcv" "1234"
# output
# argument count: 3
$@ 으로 사용할 수 있다.
func() {
echo "argument: ${@}"
}
func "1234" "qwer" "asdf zxcv"
# output
# argument: 1234 qwer asdf zxcv
${@:<시작 index>: <갯수>} 로 사용할 수 있다.
<시작 index> 는 1부터 시작한다.
<갯수> 는 생략할 수 있다. 생략하면 시작 index부터 끝까지 출력한다.
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 으로 삭제할 수 있다.
func() {
echo "func"
}
func
unset func
return 으로 종료할 수 있다.
return을 사용한다고 해서 함수의 값을 반환할 수 있는 것은 아니다.
return 후 0~255 사이의 값을 전달하여 함수의 종료코드를 전달할 수 있다.
func() {
if [ $# -eq 0 ]; then
echo "argument is empty"
return 1
fi
}
func
echo $? # 1
방법이 없다..
편법을 쓰자면 echo 를 이용하여 값을 반환하는 척 할 수는 있다.
func() {
if [[ "$@" = "qwer" ]]; then
echo "qwerty"
return
fi
echo "test"
}
var1=$(func "qwer")
echo "$var1" # qwerty
sed 를 제대로 사용할 수 있으면 정말 유용하다.
sed -n '3p' file.txt
sed -n '1,3p' file.txt
echo "${content}" | sed -n "3p"
cat file.txt | sed -n "1,3p"
sed -i '3d' file.txt
sed -i '1,3d' file.txt
사용방법
# <라인>의 <찾을문자열>을 <바꿀문자열>로 변경
sed -i "<라인>s/찾을문자열/바꿀문자열/" <파일명>
# <라인1>~<라인2>의 <찾을문자열>을 <바꿀문자열>로 변경
sed -i "<라인1>,<라인2>s/찾을문자열/바꿀문자열/" <파일명>
sed -i "3s/.*/asdf asdf/" file.txt
sed -i "2,3s/.*/asdf asdf/" file.txt
sed -i "2s/asdf/hjkl/" file.txt
사용방법
sed -i "s/바꿀대상/바꿀문자열/옵션" <변경 파일>
# 옵션
# g - 모든 대상을 변경 (global)
# i - 대소문자를 구분하지 않음 (ignore case)
# 첫 번째 대상만 변경
sed -i "s/바꿀대상/바꿀문자열/" <변경 파일>
# 매칭되는 모든 대상 변경 (global 옵션)
sed -i "s/바꿀대상/바꿀문자열/g" <변경 파일>
# 대소문자를 구분하지 않고 매칭되는 모든 대상을 변경 (global ignore case 옵션)
sed -i "s/바꿀대상/바꿀문자열/gi" <변경 파일>
sed -i "s/qwerty/asdf/" file.txt
sed -i "s/zz/oo/g" file.txt
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
바꿀 대상에 정규식을 사용할 수도 있다.
sed -i "s/[0-9]//g" file.txt
sed -i "s/asdf/oooo/gi" file.txt
구분자는 '/'가 아닌 다른 기호가 될 수도 있다.
#!/bin/bash 를 #!/usr/bin/python3로 변경sed -i "1s@#\!/bin/bash@#\!/usr/bin/python3@" file.txt
/etc/crontab을 /etc/cron.d/test로 변경sed -i "s#/etc/crontab#/etc/cron.d/test#g" file.txt
사용방법
sed -i "/찾을문자열/d" <변경 파일>
sed -i "/[0-9]/d" file.txt
사용방법
# a\ - 아래에 내용 추가
# i\ - 위에 내용 추가
# 특정 라인 밑에 내용을 추가
sed -i '<라인>a\<추가할 내용>' <변경 파일>
# 특정 라인 위에 내용을 추가
sed -i '<라인>i\<추가할 내용>' <변경 파일>
# 특정 단어 밑에 내용을 추가
sed -i -e '/<조건>/a\<추가할 내용>' <변경 파일>
* 3번째 라인 뒤에 asdf asdf를 추가
```bash
sed -i "3a\asdf asdf" file.txt
sed -i -e "/asdf/a\uiop" file.txt
bash 스크립트는 사실 sed와 awk만 잘 사용하면 거의 대부분의 작업을 할 수 있다고 생각한다.
사용방법
# 구분자 없을 시 ' ' (공백)으로 구분
awk -F '<구분자>' '{print $<출력할 컬럼>}' <파일>
<출력내용> | awk -F '<구분자>' '{print $<출력할 컬럼>}'
아래 방식대로 해도 되지만
cat file.txt | wc -l
wc -l file.txt | awk '{print $1}'
# 120 file.txt 내용을 공백으로 구분한 뒤 첫번째 컬럼을 출력하는 것이니
# 120 이 출력된다.
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 -c 를 이용하여 python 코드를 실행할 수 있다.var1=$(python3 -c "print(1.3 + 2.7)")
리눅스 메뉴얼 문서도 참고하면 좋다.
pwd로 가져오면 쉘 스크립트를 실행했을 때의 경로에 따라 위치가 달라질 수 있어 쉘 스크립트 실행 파일의 경로를 찾아야 할 때가 있다.
test.sh 쉘 스크립트가 /etc/에 존재한다고 가정할 때
cd /
/etc/test.sh # pwd 시 / 출력
cd /usr
/etc/test.sh # pwd 시 /usr 출력
cd /etc
./test.sh # pwd 시 /etc 출력
쉘 스크립트의 실행하는 위치에 따라 pwd로 가져온 경로가 달라진다.
즉, 아래 예제와 같을 경우 /etc에서 실행하지 않으면 confpath.conf 를 찾을 수 없다.
curpath=`pwd`
confpath=$($curpath/confpath.conf)
따라서 아래 스크립트 를 참고하여 쉘 스크립트가 위치해 있는 경로를 구해 confpath.conf를 찾을 수 있도록 한다.
curpath=$(dirname $(realpath $0))
cd $curpath
한줄 코딩
cd $(dirname $(realpath $0))
confpath=$($curpath/confpath.conf)