쉘 스크립트에서 Redirect(‘>’)와 Pipe(‘|’)의 차이를 간략하게 알아보자


환경 및 선수조건

  • Linux 기반 시스템에 대한 이해
  • Bash shell(/bin/bash)의 사용법


Redirect와 Pipe의 차이

Redirect

  • 프로그램 > 파일: 프로그램의 결과 혹은 출력(output)을 파일이나 다른 스트림으로 전달하거나 남길 때 사용됨

$ ps -ef > text.txt

Pipe

  • 프로그램1 | 프로그램2: 프로세스 혹은 실행된 프로그램의 결과를 다른 프로그램으로 넘겨줄 때 사용됨

$ ps -ef > text.txt


Redirect의 예시

  • 왼쪽 명령어의 결과(output)를 text.txt파일에 남깁니다.
  • 즉, 좌측의 stdout을 우측의 파일에 남깁니다.
$ ps -ef > text.txt


Pipe의 예시

  • 왼쪽 명령어의 결과(output)을 오른쪽에 있는 명령어에 입력(input)으로 전달합니다.
  • 즉, 좌측의 stdout을 우측의 stdin으로 된다고 생각하시면 됩니다.
$ ps -ef | grep bash


Redirect

  • Redirection이란 IPC(Interprocess Communication)중에 하나로 사진과 같이 standard stream을 유저가 정의한 형태(파일 형태)로 redirect해주는것을 의미합니다.


사용방법

프로그램의 결과를 파일로 저장하기

  • command의 출력물을(stdout을) filename에 기록하며 파일이 없다면 생성합니다.
  • 존재하는 파일에 추가를 하려면 >>를 사용하면 됩니다.
$ command > filename
$ command >> filename


파일을 프로그램의 입력으로 받기

  • filename을 command의 stdin으로 입력 받습니다. 즉, filename에 있는 값들로 입력을 받습니다.
$ command < filename


프로그램의 입력(공백과 개행 포함)을 직접하기

  • command에 multiline으로 입력을 보냅니다.
  • 아래와 같은 <<을 사용하면 cat을 통해서 콘솔에 space와 tab을 추가한 문자열들을 출력할수도 있고 ssh를 이용해 접속한 서버에서 명령어를 실행할 수도 있습니다.
$ command << END
abcd efdg spcace
available as standard input
END


문자열을 프로그램의 입력으로 넣기

  • 우측의 문자열을 command에 stdin의 값으로 사용합니다.
$ command <<< "string as inputs"


Standard file handle을 이용하는 예제

Error를 파일로 출력하기

  • stderr를 filename에 출력하기
  • 숫자 2는 stderr의 file descriptor 의미합니다!
  • stdin=0 stdout=1 stderr=2
$ command 2> filename


stderr를 stdout으로 출력하기

  • stderrstdout으로 출력하기
  • 1을 파일명과 구분해주기 위해서 &를 사용합니다.
$ command 2>&1 filename


응용: stderr와 stdout redirection 같이쓰기

  • stdout을 파일에 남기고 stderrstdout으로 내보내기
$ find / -name .profile > results 2>&1


Pipe

  • Pipe이란 IPC(Interprocess Communication)중에 하나로 사진과 같이 한 프로그램의 stdout을 다른프로그램의 stdin으로 전달하는 방법입니다. 즉, 한 프로그램의 출력을 다른 프로그램의 입력값으로 전달해주는 방법입니다.
  • 사진에 보이는 바와같이 stdout은 전달해주지만 stderr는 Display로 출력해줌을 알 수 있습니다.
  • Unix system call인 pipe()기반으로 만들어졌으며 buffer에다가 최대 65536 bytes (64KiB)(Linux 기준)까지 기록을 해두고 읽어가는 방식으로 구현이 되어있습니다.


사용방법

기본적인 ‘|’ 사용방법

  • |를 이용해서 왼쪽 프로그램의 실행결과를 오른쪽 프로그램으로 넘깁니다.
$ ps -ef | grep bash


참고자료

Update(2020.12.26): Add contents of parameter expansion and quote about variable

Update(2019.09.08): Add more examples to array and if statement

Simply summarize basic usage of shell script


Environment and Prerequisite

  • Linux base system
  • Bash shell(/bin/bash)


Write shell sript and allow execution permission

Make file and give permission

  • Make file
$ touch shell_script_practice.sh // create file
$ vim shell_script_practice.sh // open file with editor
  • Give execution permission(change mode)
$ chmod +x shell_script_practice.sh // add execution permission


Add #!/bin/bash to top of script

#!/bin/bash

... scripts below ...


How to run shell script

  • ./[shell scipt name]
$ ./shell_script_practice.sh


Basic usage and example

Basic stdout

  • echo, printf
echo "Echo Test" # automatically add new line
printf "printf Test" # no new line
printf "%s %s" print test # print and test are arguments
  • $#: num of arguments passed to script(like argc in C)
  • $0: name of running shell script(it includes path of shell script file when you run using path)
  • $1, $2 …: arguments passed to script(like argv[0], argv[0] in C)
#!/bin/bash

echo "Echo Test"
printf "printf Test\n"
printf "Name of script: %s\n" $0
printf "%d arguments %s %s\n" $# $1 $2


Comment

  • Use #
# echo "Echo Test"


Declare variable

  • Declare =(left is variable name and right is value) and use variable by adding $
  • Use = wihtout space!
  • Add local keyword to local variable
  • {} is parameter expansion it substitutes parameter with its value(https://superuser.com/questions/935374/difference-between-and-in-shell-script) It can be used in various ways through various expression methods.
  • It can set default value like default_value=${default_value:="example default value"} when variable value is not set or not declared.
  • Cover variable with "" so it can be more safe because we can cover space in string which is in variable. Ex) $ex -> "${ex}"
#!/bin/bash

# shell script variable
test="abc"
num=100

# variable usage
echo ${test}
echo ${num}
echo "${test}"
echo "${num}"

# local variable
local local_val="local one"

# If variable default_value is not set, set it to "example default value" and assign again.
default_value=${default_value:="example default value"}
  • If {}(parameter expansion) is not used, then variable test5678 is used and variable test is not used like below.
test=1234
echo "This is $test5678" # "This is "

echo "This is ${test}5678" # "This is 12345678"
  • If not wrapped with "", then unary operator expected error can be comes out.
  • Because its form is substitution so it is changed to if [ == " " ]
blank=" "
if [ ${blank} == " " ]
then
        echo "blank test"
fi


Array

Basic

  • array_name=(element1 element2 ...)
  • Array index starts with 0
  • array_name[@]means all array elements
#!/bin/bash

arr_test_string=("abc" "def" "ghi" "jkl")
echo "${arr_test_string[2]}"

arr_test_char=('a' 'b' 'b')
echo "${arr_test_char[0]}"

arr_test_num=(1 2 3 100 10000)
echo "${arr_test_num[3]}"

echo "${arr_test_num[@]}"

Add element to array

  • Use +=
#!/bin/bash

arr_test_string=("abc" "def" "ghi" "jkl")

arr_test_string+=("mno")
arr_test_string+=("pqr" "stu")

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

arr_test_string=(1 2 3 4 5)

arr_test_string+=(6)
arr_test_string+=(7 8)

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

Delete element in array

  • Use / operator, this operator remove all characters or strings which are included in the element.
  • (Recommended) Use unset operator
#!/bin/bash

arr_test=(1 2 3)
remove_element=(3)

arr_test=( "${arr_test[@]/$remove_element}" )

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


arr_test=("abc" "def" "ghi")
remove_element=("ghi")

arr_test=( "${arr_test[@]/$remove_element}" )

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

# !!! Be careful when you delete like below !!!
# Use second method in this case

arr_test=("abc" "def" "defghi")
remove_element=("def")

arr_test=( "${arr_test[@]/$remove_element}" )

for i in ${arr_test[@]}; do
	echo $i
done
#!/bin/bash

arr_test=("abc" "def" "defghi")
remove_element=("def")

# Get index of array
echo ${!arr_test[@]}

for i in ${!arr_test[@]}; do
	if [ ${arr_test[i]} = ${remove_element} ]; then
		# Use unset
		unset arr_test[i]
	fi
done

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


Conditional statement

#!/bin/bash

# Numeric if statement
test_num=5

if [ "${test_num}" -eq 2 ]; then
	echo "number is 2"
elif [ "${test_num}" -eq 3 ]; then
	echo "number is 3"
else
	echo "number is not 2 or 3"
fi

# Numeric if statement
test_num=5

if (( ${test_num} > 3 )); then
	echo "number is greater than 3"
else
	echo "number is not greater than 3"
fi

# String if statement
test_str="test"

if [ "${test_str}" = "test" ]; then
	echo "test_str is test"
else
	echo "test_str is not test"
fi


Iteration

  • while usage
#!/bin/bash

cnt=0
while (( "${cnt}" < 5 )); do
    echo "${cnt}"
    (( cnt = "${cnt}" + 1 )) # arithmetic operation needs (())
done
  • for usage
#!/bin/bash

arr_num=(1 2 3 4 5 6 7)

# @ in array index means all elements
for i in ${arr_num[@]}; do
    printf $i
done
echo

for (( i = 0; i < 10; i++)); do
    printf $i
done
echo


Reference

업데이트(2020.12.26): 변수 관련 parameter expansion과 따옴표 관련 내용 추가

업데이트(2019.09.08): 배열과 조건문에 예제들 추가

쉘 스크립트 사용법을 간단하게 정리하고 예제를 사용해보자


환경

  • Linux 기반 시스템
  • Bash shell(/bin/bash)


쉘 스크립트 작성 및 권한 부여

파일생성 및 권한 부여

  • 파일생성
$ touch shell_script_practice.sh // 파일 생성
$ vim shell_script_practice.sh // 쉘 스크립트 파일 편집기로 열기
  • 실행 권한 부여(파일의 상태 변경)
$ chmod +x shell_script_practice.sh // 실행 권한 부여


스크립트 상단에 #!/bin/bash 추가

#!/bin/bash

... 하단에 스크립트 작성 ...


쉘 스크립트 실행 방법

  • ./[쉘스크립트 파일명]
$ ./shell_script_practice.sh


기본 문법과 예제

기본 출력

  • echo, printf
echo "Echo Test" # 자동 개행
printf "printf Test" # 자동 개행X
printf "%s %s" print test # 뒤에 오는 문자열들이 전달되는 인자라고 생각하면 됩니다.
  • $#: 스크립트에 전달되는 인자들의 수(C언어에서 argc)
  • $0: 실행하는 스크립트의 파일명으로 실행했을 때 경로를 포함한다면 경로를 포함해서 나옵니다.
  • $1, $2 …: 스크립트로 전달된 인자들(C언어에서 argv[0], argv[1]…)
#!/bin/bash

echo "Echo Test"
printf "printf Test\n"
printf "Name of script: %s\n" $0
printf "%d arguments %s %s\n" $# $1 $2


주석

  • #를 사용
# echo "Echo Test"


변수 선언

  • =를 이용해서 선언하고 $를 이용해서 사용
  • =는 공백 없이 붙여써야한다.
  • 지역변수에는 local을 붙인다.
  • {}parameter expansion으로 $와 함께 감싼 부분에 변수를 대입해준다.(https://superuser.com/questions/935374/difference-between-and-in-shell-script) 여러 표현 방법을 통해 다양하게 사용이 가능하다.
  • 변수가 선언되지 않았을때 default_value=${default_value:="example default value"}처럼 기본값을 사용하도록 설정 가능하다.
  • ""로 감싸서 사용하면 더 안전하다. 문자열에 공백도 포함해서 값을 이용할 수 있기 때문이다. Ex) $ex -> "${ex}"
#!/bin/bash

# shell script variable
test="abc"
num=100

# variable usage
echo ${test}
echo ${num}
echo "${test}"
echo "${num}"

# local variable
local local_val="local one"

# If variable default_value is not set, set it to "example default value" and assign again.
default_value=${default_value:="example default value"}
  • 만약 {}(parameter expansion)을 사용하지 않으면 아래와 같은 경우에 변수 test가 아닌 변수 test5678을 불러오게 된다.
test=1234
echo "This is $test5678" # "This is "

echo "This is ${test}5678" # "This is 12345678"
  • 만약 ""로 감싸지 않으면 아래와 같은 조건문에서 unary operator expected라는 오류가 생길 수 있다.
  • 치환을 하는 형태이기 때문에 아래의 식이 if [ == " " ]로 바뀌기 때문이다.
blank=" "
if [ ${blank} == " " ]
then
        echo "blank test"
fi


배열

기본

  • 아래처럼 배열이름=(원소1 원소2 ...)의 형태로 선언
  • 배열의 인덱스는 0부터 시작합니다.
  • 배열이름[@]는 배열의 모든 원소를 의미합니다.
#!/bin/bash

arr_test_string=("abc" "def" "ghi" "jkl")
echo "${arr_test_string[2]}"

arr_test_char=('a' 'b' 'b')
echo "${arr_test_char[0]}"

arr_test_num=(1 2 3 100 10000)
echo "${arr_test_num[3]}"

echo "${arr_test_num[@]}"

배열에 원소 추가

  • += 연산자를 사용
#!/bin/bash

arr_test_string=("abc" "def" "ghi" "jkl")

arr_test_string+=("mno")
arr_test_string+=("pqr" "stu")

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

arr_test_string=(1 2 3 4 5)

arr_test_string+=(6)
arr_test_string+=(7 8)

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

배열에서 원소 삭제

  • /를 사용해 해당 문자열 부분이 있으면 삭제, 삭제하고자 하는 문자나 문자열이 포함되어있는 부분을 모두 삭제합니다.
  • (권고) unset을 이용해 삭제
#!/bin/bash

arr_test=(1 2 3)
remove_element=(3)

arr_test=( "${arr_test[@]/$remove_element}" )

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


arr_test=("abc" "def" "ghi")
remove_element=("ghi")

arr_test=( "${arr_test[@]/$remove_element}" )

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

# !!! Be careful when you delete like below !!!
# Use second method in this case

arr_test=("abc" "def" "defghi")
remove_element=("def")

arr_test=( "${arr_test[@]/$remove_element}" )

for i in ${arr_test[@]}; do
	echo $i
done
#!/bin/bash

arr_test=("abc" "def" "defghi")
remove_element=("def")

# Get index of array
echo ${!arr_test[@]}

for i in ${!arr_test[@]}; do
	if [ ${arr_test[i]} = ${remove_element} ]; then
		# Use unset
		unset arr_test[i]
	fi
done

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


조건문

#!/bin/bash

# Numeric if statement
test_num=5

if [ "${test_num}" -eq 2 ]; then
	echo "number is 2"
elif [ "${test_num}" -eq 3 ]; then
	echo "number is 3"
else
	echo "number is not 2 or 3"
fi

# Numeric if statement
test_num=5

if (( ${test_num} > 3 )); then
	echo "number is greater than 3"
else
	echo "number is not greater than 3"
fi

# String if statement
test_str="test"

if [ "${test_str}" = "test" ]; then
	echo "test_str is test"
else
	echo "test_str is not test"
fi


반복문

  • while문의 사용
#!/bin/bash

cnt=0
while (( "${cnt}" < 5 )); do
    echo "${cnt}"
    (( cnt = "${cnt}" + 1 )) # 숫자와 변수의 연산은 (())가 필요합니다.
done
  • for문의 사용법
#!/bin/bash

arr_num=(1 2 3 4 5 6 7)

# 배열에 @는 모든 원소를 뜻합니다.
for i in ${arr_num[@]}; do
    printf $i
done
echo

for (( i = 0; i < 10; i++)); do
    printf $i
done
echo


참고자료

Bubble Sort를 구현해보자


환경

  • Python
  • C++


Bubble Sort란?

원리

  • 인접한 2개의 값을 비교한후 값을 변경하는 과정을 통해서 정렬을 진행하는 알고리즘이다.
  • 오름차순의 경우라면 인접한 두 수를 비교했을 때 앞의 수가 더 크다면 그 둘의 값을 바꾸는 방식으로 아래의 사진을 보면 이해가 쉽다.

특징

  • 위에 사진처럼 왼쪽에서부터 오른쪽으로 진행하면서 정렬을 진행한다고 했을 때 1번 왼쪽에서 오른쪽까지 갈 때마다 가장 오른쪽에는 가장 큰 값이 위치하게 된다!
  • 코드의 구현은 쉬우나 복잡도가 O(n^2)의 성능을 나타낸다.


복잡도

  • 시간복잡도: O(n^2)
  • 공간복잡도: O(1)(swap을 위한 변수만 필요합니다.)


구현 코드(Python)


def merge_sort_better(A):

    for i in range(len(A) - 2, 0, -1):
        for j in range(0, i + 1):
            if A[j] > A[j + 1]:
                tmp = A[j]
                A[j] = A[j + 1]
                A[j + 1] = tmp

    return A


def merge_sort_normal(A):

    for i in range(0, len(A)):
        for j in range(0, len(A) - 1):
            if A[j] > A[j + 1]:
                tmp = A[j]
                A[j] = A[j + 1]
                A[j + 1] = tmp
    return A

import random
A = [random.randint(0, 100) for _ in range(0, 10)]
print(A)
merge_sort_better(A)
print(A)



구현 코드(C, C++)

#include <stdio.h>
#include <stdlib.h>
#include <time.h>

void swap(int *a, int *b) {
	int tmp = *a;
	*a = *b;
	*b = tmp;
}

void bubble_sort_better(int a[], int len) {

	for (int i = len - 2; i >= 0; --i) {
		for (int j = 0; j <= i; ++j) {
			if (a[j] > a[j + 1]) {
				swap(&a[j], &a[j + 1]);
			}
		}
	}

}

void bubble_sort_normal(int a[], int len) {

	for (int i = 0; i < len; ++i) {
		for (int j = 0; j < len - 1; ++j) {
			if (a[j] > a[j + 1]) {
				swap(&a[j], &a[j + 1]);
			}
		}
	}

}

int main() {

	int a[5];
	int len = sizeof(a) / sizeof(a[0]); // 배열길이

	// 랜덤함수를 위한 srand와 seed
	srand(time(NULL));

	// 벡터 초기화
	for (int i = 0; i < len; i++) {
		// 1 ~ 50까지의 난수 생성
		a[i] = rand() % 50 + 1;
	}

	// 정렬 전 출력
	for (int i = 0; i < len; i++) {
		printf("%d ", a[i]);
	}
	printf("\n");

	// 거품정렬
	bubble_sort_better(a, len);

	// 정렬 후 출력
	for (int i = 0; i < len; i++) {
		printf("%d ", a[i]);
	}
	printf("\n");

	return 0;
}


참고자료

진행하고 있는 장고 프로젝트에 cron 작업을 추가하는 경험을 정리해본다.


환경 및 선수조건

  • Django
  • crontab의 개념
  • (선택) pyenv와 virtualenv를 이용한 가상환경(가상환경에서도 잘 돌아가고 경로 설정을 자동으로 해줍니다.)


crontab 사용 이유

문제

  • 기존 API를 호출하면 페이지를 크롤링을 하고 그에 대한 결과를 응답하는데 시간이 3초 이상걸린다.

해결

  • 크롤링하고자하는 페이지를 주기적으로 크롤링해서 결과를 DB에 저장해두고 API는 DB에 저장된 값들만 가져온다.


설정

pip를 통한 설치

pip install django-crontab


settings.py에 django_crontab를 추가

settings.py

...
INSTALLED_APPS = (
    'django_crontab',
    ...
)
...


app/cron.py의 파일을 생성 및 함수 만들기

  • 해당 장고프로젝트의 app(본인의 경우에는 main이라는 앱명을 지었습니다.)폴더 안에 cron.py 파일을 생성하고 함수를 만듭니다.
  • 사실 함수를 불러올 수 있는 경로이면 상관 없습니다. 즉, 따로 cron.py를 만들지 않고 경로만 잘 잡아주면 됩니다.

app/cron.py

def my_cron_job():
    pass


settings.py에 있는 CRONJOBS에 작업 추가하기

  • 기본 Linux crontab과 형식이 같습니다.(참고)
  • 기본 구조: * * * * * 수행할 명령어
  • 순서대로 분(0 - 59) 시(0 - 23) 일(1 - 31) 월(1 - 12) 요일(0 - 6) (0:일요일, 1:월요일, 2:화요일, …, 6:토요일)
  • 예제: ('*/5 * * * *', 'app.cron.my_cron_job') -> 매 5분마다 app/cron.py에 있는 my_cron_job()을 실행합니다.

settings.py

...
CRONJOBS = [
    ('*/5 * * * *', 'app.cron.my_cron_job')
]
...


적용

등록된 job들을 모두 실행

python manage.py crontab add

등록된 job들을 모두 제거

python manage.py crontab remove

등록된 job들을 모두 보기

  • django-crontab을 통해서 등록한 job들이 보여집니다.
python manage.py crontab show


확인

  • 위에 나와있는 명령어로 확인이 가능하지만 django-crontab의 경우에는 등록을 하면 Linux crontab에 등록이 된다.
  • crontab -e의 명령어를 통해서 확인이 가능하다.


실수

  • django-crontab을 이용하는 경우에 실제 리눅스에 등록된 crontab이 작동하는거기 때문에 파일의 경로에 신경을 써줘야합니다.
  • 예를 들어서, 위에 cron.py에서 파일을 새로 생성했다면 프로젝트의 앱 폴더(app/)가 아닌! 리눅스 유저 기본 디렉토리(~)에 위치합니다!


참고자료