업데이트(2019.02.03): 코드 일부 수정 및 개선

C++에서 단순 열결 리스트(Singly Linked List)를 구현해보자


환경

  • C++


Singly Linked List란?

개념

  • 아래처럼 하나의 노드에 필요한 정보를 담고 다음에 해당하는 노드를 가리키고 있는 자료구조로 포인터를 이용해 자료들을 선형으로 연결할 자료구조이다.
  • 배열과 비교했을 때 추가 및 삭제가 쉽다는 장점이 있지만 접근할 때 O(n)만큼 걸린다는 단점이 있습니다.



구현

공통

  • 구현을 편리하게 하기위해 전역으로 하나의 리스트만 만들었습니다.
  • 큰 틀은 아래와 같으면 추가, 삭제 그리고 순회의 경우에 필요한 부분들을 수정하면 됩니다.
  • list는 리스트의 처음을 가리킵니다.
#include <stdio.h>
#include <stdlib.h>

// Node
struct Node {
	int data;
	Node * next;
};

// Global list
Node * list;


추가/삭제 공통

  • 추가와 삭제의 경우에 첫번째 노드인지 아닌지를 고려해주면 구현이 편합니다.
  • 리스트의 중간에서 자료의 추가나 제거가 이루어질 때 현재 노드를 가리키는 cur와 이전 노드를 가리키는 prev가 있으면 추가 및 삭제가 편합니다.
  • 삭제의 경우에 삭제하고자 하는 노드가 있으면 true를 반환하고 없다면 false를 반환합니다.
  • 함수 ascending_order_add의 경우에는 오름차순으로 리스트를 생성하는 예제 함수입니다.


추가

  • 아래처럼 추가하려는 노드의 위치를 찾은 후에 새 노드의 next를 다음 노드를 가리키고 이전 노드가 추가하려는 노드를 가리키게 합니다.
  • 코드는 크게 3가지 방식이 있습니다.
  • add: 새로 추가하는 방법
  • ascending_order_add: 중간에 추가하는 방법
  • add_unique: 중복된 값 없이 추가하는 방법



// Add - one by one
void add(int key) {

	Node * new_node = (Node*)malloc(sizeof(Node));
	new_node->data = key;
	new_node->next = NULL;

	// Check first element
	if (list == NULL) {
		list = new_node;
	}
	else {
		// Add new node to head
		new_node->next = list;
		list = new_node;
	}
}
// Add - add ascending order
void ascending_order_add(int key) {

	Node * new_node = (Node*)malloc(sizeof(Node));
	new_node->data = key;
	new_node->next = NULL;

	if (list == NULL) {
		list = new_node;
	}
	else {

		Node * cur = list;
		Node * prev = NULL;

		// If first element is larger than key
		if (cur->data > new_node->data) {
			new_node->next = cur;
			list = new_node;
		}
		// Other cases
		else {
			while (cur != NULL && cur->data < new_node->data) {
				prev = cur;
				cur = cur->next;
			}
			// Add in middle
			if (cur != NULL) {
				new_node->next = cur;
				prev->next = new_node;
			}
			// Add to end
			else {
				prev->next = new_node;
			}
		}
	}
}
// Add - add only unique value
void add_unique(int key) {
	Node * new_node = (Node*)malloc(sizeof(Node));
	new_node->data = key;
	new_node->next = NULL;

	if (list == NULL) {
		list = new_node;
	}
	else {
		Node * cur = list;

		// Duplication check
		while (cur != NULL) {
			if (cur->data == key) {
				return;
			}
			cur = cur->next;
		}

		new_node->next = list;
		list = new_node;
	}
}


삭제

  • 아래처럼 삭제하려는 노드의 위치를 찾은 후에 이전노드가 삭제하려는 노드의 다음 노드를 가리키도록 하면 됩니다.
  • 똑같이 처음을 주의해주고 쉽게 구현하기 위해 curprev을 이용합니다.



// Remove
bool remove(int key) {

	if (list == NULL) {
		return false;
	}

	if (list->data == key) {
		Node * cur = list;
		list = list->next;
		free(cur);
		return true;
	}
	else {
		Node * cur = list->next;
		Node * prev = list;
		while (cur != NULL && cur->data != key) {
			prev = cur;
			cur = cur->next;
		}

		if (cur == NULL) return false;

		prev->next = cur->next;
		free(cur);
		return true;
	}
}


코드

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

// Node
struct Node {
	int data;
	Node * next;
};

// Global list
Node * list;

// Init
void init() {

	if (list == NULL) {
		return;
	}
	else {
		Node * cur;
		cur = list;

		while (cur != NULL) {
			list = cur->next;
			free(cur);
			cur = list;
		}
	}
}

// Add - one by one
void add(int key) {

	Node * new_node = (Node*)malloc(sizeof(Node));
	new_node->data = key;
	new_node->next = NULL;

	// Check first element
	if (list == NULL) {
		list = new_node;
	}
	else {
		// Add new node to head
		new_node->next = list;
		list = new_node;
	}
}

// Add - add ascending order
void ascending_order_add(int key) {

	Node * new_node = (Node*)malloc(sizeof(Node));
	new_node->data = key;
	new_node->next = NULL;

	if (list == NULL) {
		list = new_node;
	}
	else {

		Node * cur = list;
		Node * prev = NULL;

		// If first element is larger than key
		if (cur->data > new_node->data) {
			new_node->next = cur;
			list = new_node;
		}
		// Other cases
		else {
			while (cur != NULL && cur->data < new_node->data) {
				prev = cur;
				cur = cur->next;
			}
			// Add in middle
			if (cur != NULL) {
				new_node->next = cur;
				prev->next = new_node;
			}
			// Add to end
			else {
				prev->next = new_node;
			}
		}
	}
}

// Add - add only unique value
void add_unique(int key) {
	Node * new_node = (Node*)malloc(sizeof(Node));
	new_node->data = key;
	new_node->next = NULL;

	if (list == NULL) {
		list = new_node;
	}
	else {
		Node * cur = list;

		// Duplication check
		while (cur != NULL) {
			if (cur->data == key) {
				return;
			}
			cur = cur->next;
		}

		new_node->next = list;
		list = new_node;
	}
}

// Remove
bool remove(int key) {

	if (list == NULL) {
		return false;
	}

	if (list->data == key) {
		Node * cur = list;
		list = list->next;
		free(cur);
		return true;
	}
	else {
		Node * cur = list->next;
		Node * prev = list;
		while (cur != NULL && cur->data != key) {
			prev = cur;
			cur = cur->next;
		}

		if (cur == NULL) return false;

		prev->next = cur->next;
		free(cur);
		return true;
	}
}

// Traverse
void traverse() {

	Node * cur = list;
	while (cur != NULL) {
		printf("%d ", cur->data);
		cur = cur->next;
	}
	printf("\n");

}

int main() {

	int arr[9] = { 2, 4, 6, 8, 1, 3, 5, 7, 9 };
	int arr_duplicated[18] = { 8, 1, 3, 2, 4, 6, 8, 1, 3, 5, 7, 9, 2, 4, 6, 5, 7, 9 };
	int arr_rmv[4] = { 2, 9, 8, 100 };


	// Add to list 1
	init();
	for (int i = 0; i < sizeof(arr) / sizeof(arr[0]); ++i) {
		add(arr[i]);
	}
	printf("After add(normal): ");
	traverse();


	// Add to list 2
	init();
	for (int i = 0; i < sizeof(arr) / sizeof(arr[0]); ++i) {
		ascending_order_add(arr[i]);
	}
	printf("After add(ascending): ");
	traverse();


	// Add to list 3
	init();
	for (int i = 0; i < sizeof(arr_duplicated) / sizeof(arr_duplicated[0]); ++i) {
		add_unique(arr_duplicated[i]);
	}
	printf("After add(unique): ");
	traverse();


	// Remove specific values in list
	for (int i = 0; i < sizeof(arr_rmv) / sizeof(arr_rmv[0]); ++i) {
		remove(arr_rmv[i]);
	}
	printf("After remove: ");
	traverse();

	return 0;

}

실행결과

After add(normal): 9 7 5 3 1 8 6 4 2
After add(ascending): 1 2 3 4 5 6 7 8 9
After add(unique): 9 7 5 6 4 2 3 1 8
After remove: 7 5 6 4 3 1


참고자료

Linux 기반 운영체제에서 tee 명령어를 사용해 화면과 파일에 동시에 출력해보자


환경 및 선수조건

  • Linux
  • Bash shell(/bin/bash)


tee 명령어

  • tee 명령어는 다음 아래 사진처럼 명령어의 출력 결과를 파일과 화면에 동시에 출력할 수 있도록 해주는 명령어입니다.
  • stdin을 받아서 stdout과 하나 이상의 파일에 그 입력을 출력하는겁니다.

기본 사용법

  • [ -a ]: 덮어쓰기 말고 해당 파일에 추가해서 입력합니다.
  • [ -i ]: interrupt를 무시하는 옵션
  • [ File ... ]: 파일들 이름입니다.

tee [ -a ] [ -i ] [ File ... ]


예제 - 명령어의 결과를 파일과 stdout으로 출력하기

$ echo test | tee tee-test-file.txt
test
$ cat tee-test-file.txt
test


예제 - shell에서 파일 생성하기

$ tee tee-test-file.txt << EOF
> Multi line test
> 1
> 2
> 3
> End!
> EOF
Multi line test
1
2
3
End!
$ cat tee-test-file.txt
Multi line test
1
2
3
End!


참고자료

쉘 스크립트에서 함수나 스크립트의 실행결과를 받아보자


환경 및 선수조건

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


Shell에서의 반환값?

Shell Script에서의 값 반환

  • 일반적으로 shell script에서는 우리가 아는 컴퓨터 언어에서의 return 반환값이 없습니다.
  • shell script가 실행되는 프로세스에서 exit을 통해 상태 종료 표시만을 프로세스 실행 결과로 반환할 수 있습니다.
  • 일반적으로 0은 성공을 나타내며 나머지인 1 ~ 255는 에러를 나타냅니다.


exit의 예시 확인

  • 간단하게 예시를 확인해 보겠습니다.
  • 아래처럼 없는 명령어를 아무거나 입력하고 실행해서 오류가 떴음을 확인하고 $?를 통해서 방금 명령어에 대한 exit code를 확인합니다.
  • $?는 방금 실행된 프로세스가 반환한 결과값을 저장하고 있습니다.
$ error_command
error_command: command not found
$ echo $?
127


Shell 스크립트 함수에서 결과값 받기

  • 일반적인 언어처럼 return과 같은 결과를 얻고 싶을 때는 2가지 방법을 사용할 수 있습니다.
  • $(명령어 or 쉘 스크립트 실행 or 쉘 스크립트 함수)와 같이 $()안에 명령어, 쉘 스크립트 또는 쉘 스크립트 함수를 넣으면 해당 부분들을(명령어, 쉘 스크립트 또는 쉘 스크립트 함수) 실행할 subshell을 호출합니다.
  • 해당 $()를 통한 subshell 생성은 부모 shell의 변수나 값들을 가져오기 때문에 함수도 변수도 다 사용할 수 있습니다. 다만, subshell의 결과가 부모 shell에 영향을 주지는 않습니다.
  • subshell을 호출하지만 $$를 통해서 PID를 출력하면 parent shell의 프로세스 아이디를 호출합니다.


(방법1) echo를 통해서 값 전달 받기

  • 다음 아래처럼 subshell로 함수나 명령어를 실행해서 결과를 가져올 수 있습니다.
#!/bin/bash

get_result_func () {
	test=123456
	# echo 함수를 통해서 결과를 전달
	# return "Result is ${test}"라고 생각하시면 됩니다.
	echo "Result is ${test}"
}

# 다음 아래와 같이 함수 호출의 결과를 변수에 받습니다.
ret_value=$(get_result_func)

echo $ret_value
$ ./shell_script_practice.sh
Result is 123456


(방법2) 변수 공유하기

  • 다음처럼 변수를 전역으로 선언하고 해당 변수를 이용해서 사용할 수 있습니다.
#!/bin/bash

ret_value=""

get_result_func () {
	# Do Something
	ret_value="aaaaaaaaa"
}

get_result_func
echo $ret_value
$ ./shell_script_practice.sh
aaaaaaaaa


참고자료

쉘 스크립트에서 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