셸 스크립트 프로그래밍

Yunes·2024년 4월 12일

linux study

목록 보기
1/3
post-thumbnail

bash 셸

bash : Bourne Again SHell

bash 셸은 명령어를 해석해서 실행해준다.

bash 셸의 특징

  • Alias - 명령어 단축 기능
  • History 기능 - 위/아래 단축키
  • 연산 기능
  • Job Control 기능
  • 자동 이름 완성 기능 (탭키)
  • 프롬프트 제어 기능
  • 명령 편집 기능

셸의 명령문 처리 방법

  • (프롬프트) 명령어 [옵션...][인자...]
  • rm -rf /mydir

환경변수

#!/bin/bash

"echo $환경변수이름" # 환경변수 확인
"export 환경변수=값" # 환경변수 값 변경
환경변수설명환경변수설명
HOME사용자의 홈 디렉토리 경로를 지정합니다.LANG프로그램의 언어 설정을 지정합니다.
TERM터미널 타입을 지정합니다.USER현재 사용자의 이름을 지정합니다.
COLUMNS터미널의 열 수를 지정합니다.PS1프롬프트에서 표시되는 첫 번째 문자열을 지정합니다.
BASHBash 쉘의 경로를 지정합니다.HISTFILE히스토리 파일의 경로를 지정합니다.
HOSTNAME시스템의 호스트 이름을 지정합니다.LOGNAME현재 사용자의 로그인 이름을 지정합니다.
MAIL메일함의 경로를 지정합니다.PATH실행 파일이 위치한 디렉토리들을 쉼표로 구분하여 지정합니다.
PWD현재 작업 중인 디렉토리의 경로를 지정합니다.SHELL현재 사용 중인 쉘의 경로를 지정합니다.
DISPLAYX 윈도 시스템 디스플레이의 주소를 지정합니다.LINES터미널의 행 수를 지정합니다.
PS2프롬프트에서 표시되는 두 번째 문자열을 지정합니다.BASH_VERSIONBash 쉘의 버전을 지정합니다.
HISTSIZE히스토리 파일에 저장되는 명령어의 최대 개수를 지정합니다.USERNAME현재 사용자의 이름을 지정합니다.
LS_COLORSls 명령의 색상 설정을 지정합니다.OSTYPE현재 운영 체제의 타입을 지정합니다.

개인적으로 무언가 학습할때 글로만, 말로만 듣고서는 이해가 잘 안되는 편이다.
그래서 하나씩 직접 확인해봤다.

리눅스에서도 확인이 가능하겠지만 맥 os 를 쓰고 있기도 하고 visual studio code 로 하나씩 쳐봤다.

환경변수설명환경변수설명
HOME사용자의 홈 디렉토리 경로를 지정합니다.LANG프로그램의 언어 설정을 지정합니다.
TERM터미널 타입을 지정합니다.USER현재 사용자의 이름을 지정합니다.
COLUMNS터미널의 열 수를 지정합니다.PS1프롬프트에서 표시되는 첫 번째 문자열을 지정합니다.
BASHBash 쉘의 경로를 지정합니다.HISTFILE히스토리 파일의 경로를 지정합니다.
HOSTNAME시스템의 호스트 이름을 지정합니다.LOGNAME현재 사용자의 로그인 이름을 지정합니다.
MAIL메일함의 경로를 지정합니다.PATH실행 파일이 위치한 디렉토리들을 쉼표로 구분하여 지정합니다.
PWD현재 작업 중인 디렉토리의 경로를 지정합니다.SHELL현재 사용 중인 쉘의 경로를 지정합니다.
DISPLAYX 윈도 시스템 디스플레이의 주소를 지정합니다.LINES터미널의 행 수를 지정합니다.
PS2프롬프트에서 표시되는 두 번째 문자열을 지정합니다.BASH_VERSIONBash 쉘의 버전을 지정합니다.
HISTSIZE히스토리 파일에 저장되는 명령어의 최대 개수를 지정합니다.USERNAME현재 사용자의 이름을 지정합니다.
LS_COLORSls 명령의 색상 설정을 지정합니다.OSTYPE현재 운영 체제의 타입을 지정합니다.
  • 실행 결과

AMI 에서 ubuntu 나 linux 의 bash 셸을 사용했을 때와 visual studio code 에서 zsh 를 사용했을 때의 결과가 약간 다르게 나타났다. 그래도 기본적으로는 거의 유사한 결과를 볼 수 있었다.

셸 스크립트 특징

  • c 언어와 유사하게 프로그래밍이 가능
  • 변수, 반복문, 제어문 등의 사용이 가능
  • 스크립트 언어이기에 별도로 컴파일하지 않고 텍스트 파일 형태로 바로 실행
  • vi 나 gedit 으로 작성 가능
  • 리눅스의 많은 부분이 셸 스크립트로 작성되어 있음

실행 방법

  1. sh 스크립트 파일
  2. chmod +x 스크립트 파일 로 실행 권한 부여후 ./스크립트 파일 명령으로 실행

변수

  • 변수를 사용전 미리 선언하지 않음
  • 변수에 처음 값이 할당되며 자동으로 변수가 생성됨
  • 모든 변수는 문자열(String)로 취급
  • 변수 이름은 대소문자 구분
  • 변수를 대입시 '=' 좌우에 공백이 없어야 함
  • 만약 공백이 있는 값을 변수에 할당하고자 한다면 "" 로 감싸줘야 한다.
#!/bin/bash

val="Yes Sir"
echo $val # Yes Sir

val=7+5
echo $val # 7+5

변수의 입출력

  • $ 문자가 들어간 글자를 출력하려면 ' ' 로 묶어주거나 앞에 \ 를 붙인다.
  • " " 로 변수를 묶어줘도 된다.
  • 변수의 입력을 받는 예시코드
#!/bin/bash

echo 값 입력 :
read myvar
echo '$myvar' = $myvar # 입력한게 test 였다면 $myvar = test 출력

숫자 계산

  • 변수에 대입된 값은 모두 문자열로 취급
  • 변수에 들어 있는 값을 숫자로 하여 +, -, *, / 등의 연산을 하기 위해 expr 사용
  • 수식에 괄호 혹은 곱하기는 그 앞에 역슬래쉬 \ 를 붙인다.
#!/bin/bash

num1=100
num2=$num1+200
echo $num2 # 100+200
num3=`expr $num1 + 200`
echo $num3 # 300
num4=`expr \($num1 + 200 \) / 10 \* 2`
echo $num4 # 60 ?

num5=$((($num1 + 200) / 10 * 2))
echo $num5 # 60

num6=`expr \( $num1 + 200 \) / 10 \* 2`
echo $num6 # 60
  • 명령어 실행은 ` ` 로 감싸야 실행된다.
  • 그런데 강의에서는 num4 가 60이라고 나온다고 해서 지금 해보니 linux 에서는 expr: non-integer argument 라고 뜬다.
  • 오류의 원인을 제대로 확인해보니 shell 에서 연산은 연산자, 피연산자 사이에 공백이 반드시 있어야 한다. 그게 없으면 문법 에러가 뜬다던지 위와 같이 num4 의 경우 괄호와 num1 사이에 공백이 없어서 제대로 연산이 되지 않았던 것이다.

파라미터 변수

  • 파라미터 변수는 $0, $1, $2 ... 의 형태를 갖는다.
  • 전체 파라미터는 $* 로 표현한다.

기본 if 문

  • 형식
#!/bin/bash

# if [ 조건 ]; then
# 	참일 경우 실행
# fi

if [ "A" = "A" ]; then # 셸에서는 = 나 == 나 둘다 동등연산자처럼 같은지 비교할때 사용 가능하다.
	echo "true"
fi

# if [ 조건 ]; then
# 	참일 경우 실행
# elif
# 	temp
# else
# 	temp
# fi

찾다보니 bash script 에서의 연산자를 잘 모르고 있어서 이 기회에 한번 확인해봤다.

정수 비교

연산자의미표현
-eq같음$a -eq $b
-ne같지 않음$a -ne $b
-gt보다 큼$a -gt $b
-ge이상$a -ge $b
-lt미만$a -lt $b
-르작거나 같음$a -le $b
>보다 큼(숫자 비교)[[ $a > $b ]]
>=크거나 같음(숫자 비교)[[ $a >= $b ]]
<미만(숫자 비교)[[ $a < $b ]]
<=작거나 같음(숫자 비교)[[ $a -le $b ]]

문자열 비교

연산자의미표현
=Equal"$string1" = "$string2"
==Equal"$string1" == "$string2"
!=Not equal"$string1" != "$string2"
-zZero length-z "$string"
-nNon-zero length-n "$string"
>Greater than"$string1" > "$string2"
<Less than"$string1" < "$string2"
  • -n, -z 의 경우 각각 문자열이 존재하는지, 문자열이 없는지 등의 조건을 사용하는데에 자주 사용되는 비교연산자들이라 잘 알고 있어야 한다.

참고

  • [[ "$a" == a* ]] : $a 가 "a" 로 시작하면 참 (패턴)
  • [[ "$a" == "a*" ]] : $a 가 "a*" 와 같으면 참
  • [ "$a" == a* ] : 잘못된 표현
  • [ "$a" == "a*" ] : $a 가 "a*" 와 같으면 참

=> 셸을 쭉 써오면서 변수를 사용하는데 어떤 경우 "" 로 감쌀 때와 그렇지 않을때 차이가 발생하는 부분에 대해 왜 그런 것인지 의문을 가졌었다. 그 각각의 케이스를 모두 나타내지는 않는 예시지만 일부 의문점이 해소될 수 있는 부분이다.

복합 비교

OperatorMeaningExpression
-aLogical ANDif [[ condition1 -a condition2 ]]
-oLogical ORif [[ condition1 -o condition2 ]]
&&Logical ANDif [[ command1 && command2 ]]
||Logical ORif [[ command1 || command2 ]]

파일 비교

연산자의미표현식
-d디렉토리 존재 여부if [[ -d $path ]]
-f파일 존재 여부if [[ -f $path ]]
-e존재 여부if [[ -e $path ]]
-r읽기 가능 여부if [[ -r $path ]]
-w쓰기 가능 여부if [[ -w $path ]]
-x실행 가능 여부if [[ -x $path ]]
-s크기 여부if [[ -s $path ]]
-O소유자 여부if [[ -O $path ]]
-G그룹 소유자 여부if [[ -G $path ]]
-nt최신 여부if [[ $file1 -nt $file2 ]]
-ot이전 여부if [[ $file1 -ot $file2 ]]

case ~ esac 문

셸에는 switch case 문이 아니라 case ~ esac 문이 있다.

  • case1)
#!/bin/bash

echo "Enter a fruit: "
read fruit

case $fruit in
    apple)
        echo "You chose an apple."
        ;;
    banana)
        echo "You chose a banana."
        ;;
    orange|kiwi)
        echo "You chose an orange or a kiwi."
        ;;
    *)
        echo "You chose something else."
        ;;
esac
  • case2)
#!/bin/bash

echo "Do you like programming? (yes/no)"
read answer

case $answer in
    yes | y | Y | Yes | YES )
        echo "Great! Programming is fun!"
        ;;
    [nN]*)
        echo "That's okay. Programming isn't for everyone."
        ;;
    *)
        echo "Please answer yes or no."
        ;;
esac

반복문 - for 문

#!/bin/bash

# for 변수 in 값1 값2 값3 ...; do
# 	반복할 명령어
# done

# Define an array of fruits
fruits=("apple" "banana" "orange" "kiwi" "grape")

echo "List of fruits:"

# Iterate over each fruit in the array using a for loop
for fruit in "${fruits[@]}"; do
    echo "$fruit"
done


for file in test.sh; do
    echo "File: $file"
    echo "---------------------"
    # Print the first three lines of the file
    head -3 "$file"
    echo "---------------------"
done
  • head -3 "$file" 은 해당 파일의 앞에서부터 3번째 줄까지의 내용을 출력해준다. 그래서 따로 echo 를 하지 않아도 출력되며 만약 해당 파일이 .sh 형식의 셸 파일이었다면 맨 앞줄의 #!/bin/bash 부터 카운트해서 빈 줄도 다 포함하여 3번째 줄까지 출력한다.

반복문 - while 문

  • 조건식이 참인 동안에 계속 반복한다.
#!/bin/bash

count=1

echo "Counting from 1 to 5 using a while loop:"

# Using a while loop to count from 1 to 5
while [ $count -le 5 ]; do
    echo "Count: $count"
    ((count++))  # Increment the count variable
done

echo "Finished counting."

셸도 조건식 내에 1 혹은 : 를 붙이면 true 로 평가된다. 따라서 [1] 또는 [:] 가 조건식으로 사용되면 항상 참이된다.

until 문

  • while 문과 용도가 거의 같으나 until 문은 조건식이 참일 때까지 계속 반복한다.
#!/bin/bash

count=1

echo "Counting from 1 to 5 using an until loop:"

# Using an until loop to count from 1 to 5
until [ $count -gt 5 ]; do
    echo "Count: $count"
    ((count++))  # Increment the count variable
done

echo "Finished counting."

break, continue, exit, return 문

  • break 는 반복문을 종료할 때 사용된다.
  • continue 는 반복문의 조건식으로 돌아가게 한다.
  • exit 는 해당 프로그램을 완전히 종료한다.
  • return 은 함수 안에서 사용될 수 있고 함수를 호출한 곳으로 돌아가게 한다.

사용자 정의 함수

#!/bin/bash

# Define a custom function
function greet() {
    echo "Hello, $1!"
}

# Call the function with an argument
greet "Alice"

나는 개인적으로 셸에서도 호이스팅이 적용될지 궁금했다.
확인해봤을때는 적용되지는 않는 것 같았다.

그런데 function 정의 - 변수 정의 - 함수 호출의 순서일 경우
함수가 해당 변수를 사용할 경우 이 변수는 함수 정의문보다 앞서 선언 및 사용된 적이 없으나 함수 사용 시점은 변수 정의 이후에 발생한 이벤트라 문제없이 동작한다.

eval

  • 문자열을 명령문으로 인식하고 실행한다.
  • 단, 보안상 취약점이 있어 무턱대고 웹에 사용해서는 굉장히 위험성이 있어 사용하지 않는 추세라고 한다.
#!/bin/bash

# Define a variable containing a command
command="echo Hello, World!"

# Execute the command directly
echo "Executing command directly:"
$command

# Use eval to execute the command stored in a variable
echo "Using eval:"
eval "$command"

export

  • 외부 변수로 선언해준다.
  • 선언한 변수를 다른 프로그램에서도 사용할 수 있도록 해준다.

exp1.sh

#!/bin/bash

# Define an environment variable
export MESSAGE="Hello from exp1.sh!"

# Execute exp2.sh
./exp2.sh

exp2.sh

#!/bin/bash

# Access the environment variable defined in exp1.sh
echo "Message from exp2.sh: $MESSAGE"

printf

  • c 언어의 printf() 함수와 비슷하게 형식을 지정해서 출력해줄 수 있다.
#!/bin/bash

# Define variables
name="Alice"
age=30
height=165.5

# Using printf to format and print output
printf "Name: %s\n" "$name" # Name: Alice
printf "Age: %d\n" "$age" # Age: 30
printf "Height: %.1f\n" "$height" # Height: 165.5

set 과 $(명령어)

  • 리눅스 명령어를 결과로 사용하기 위해서는 $(명령어) 형식을 사용한다.
  • 결과를 파라미터로 사용하고자 할 때는 set 과 함께 사용한다.
#!/bin/bash

echo "오늘 날짜는 $(date) 입니다." # 오늘 날짜는 Sat Apr 13 00:50:18 KST 2024 입니다.
set $(date)
echo "오늘은 $1 요일 입니다." # 오늘은 Sat 요일 입니다.

shift

  • 파라미터 변수를 왼쪽으로 한 단계씩 아래로 쉬프트 시킨다.
  • 10 개가 넘는 파라미터 변수에 접근시 사용한다
  • 단, $0 파라미터 변수는 변경되지 않는다.
#!/bin/bash

echo "Iterating through command-line arguments using shift:"

# Loop through all command-line arguments
while [ $# -gt 0 ]; do
    echo "Argument: $1"
    shift
done

실생활에서 활용해보기

나는 2023년 10월부터 2024년 4월 초까지 6개월간 백준 알고리즘 스터디를 운영해왔다.

그 과정에서 반복적으로 수행되는 작업들에 대해 셸을 통해 자동화할 수 없을까 하는 생각을 하게 되었고 먼저 반복적으로 해야 하는 작업들을 정리해봤다.

  • 매주 몇주차 폴더를 만들고 그 안에서 문제이름 폴더를 만들고 그 안에 각 문제이름을 찾아 문제 폴더를 생성해준다.
  • 매주 스터디원별로 문제를 적절히 할당한다. - 기존엔 네이버 사다리타기를 사용
  • 할당된 문제를 노션등에 공지한다.

이 과정을 자동화했다.

먼저 어떻게 했을까?

필요한 작업은 다음과 같다.

  • 링크만 주면 문제 이름을 추출하기
  • 문제 리스트를 랜덤으로 매칭하기

되게 간단해 보이는데 실제로 구현하기까지 4주정도 걸렸다. ( 사실 주말중 일요일 하루만 투자해서 4주 정도 ... )

문제 리스트 뽑기

이 부분은 주차별 주제를 고려하고 적절성 등을 판단하여 뽑아야 했기 때문에 수작업으로 했다.

problems.txt

https://www.acmicpc.net/problem/15886
https://www.acmicpc.net/problem/16945
https://www.acmicpc.net/problem/16987
https://www.acmicpc.net/problem/16938
https://www.acmicpc.net/problem/7490
https://www.acmicpc.net/problem/3190
https://www.acmicpc.net/problem/12100

백준 문제번호로 문제이름 추출하기

crawling.py

# developed by Jeonhui Lee
# pip3 install requests
# pip3 install bs4

'''
Baekjoon 문제 번호로 문제 불러오기
'''

import sys

def get_baekjoonproblem(problem_number):
  
  import requests
  from bs4 import BeautifulSoup as Soup
  # problem_number = sys.stdin.readline().rstrip()
  # 문제 번호 입력

  try:
    headers = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.77 Safari/537.36'}
    soup = Soup(requests.get('https://www.acmicpc.net/problem/' + problem_number, headers=headers).text, 'html.parser')

    # 문제 주소의 html 파일을 text로 불러옴
    title = soup.find('span', {'id': 'problem_title'}).text

    # id가 problem_title인 span태그를 찾음
    return title

  except Exception as e:
    # 잘못된 입력이 들어왔을 시에 problem_tilte을 찾지 못함 -> 종료
    print("잘못된 문제 번호입니다.", e)
    return

if __name__ == '__main__':
  problem_number = sys.argv[1]

  problem_title=get_baekjoonproblem(problem_number)
  print(problem_title)

위의 코드는 구글링중 Jeonhui Lee 님 블로그에 공유된 코드중 문제이름을 추출하는 부분만 사용한 코드다. 다시 구글링해보니 어디서 확인했는지 보기 어려워 링크를 못찾았다..

프로그래머스 문제 링크로 문제번호 추출하기

스터디를 운영하다보니 실제 코테에서는 프로그래머스 환경에서 치뤄지는 경우가 많다보니 매주 7문제씩 푸는 것중 한문제는 프로그래머스 문제를 선정했다.

그러다보니 백준 문제이름만 추출해서는 프로그래머스 문제 이름을 같이 추출하지는 못하는 문제가 있어 이 경우 역시 구글링으로 찾아봤다.

crawling_psg.py

# 참고한 레퍼런스 : https://velog.io/@a101201031/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-%EB%AC%B8%EC%A0%9C-%ED%81%AC%EB%A1%A4%EB%A7%81%ED%95%B4%EC%84%9C-%EB%A7%88%ED%81%AC%EB%8B%A4%EC%9A%B4%EC%9C%BC%EB%A1%9C-%EB%B3%80%ED%99%98%ED%95%98%EA%B8%B01

import os
import sys
import requests
from bs4 import BeautifulSoup
from markdownify import MarkdownConverter as MarkdownifyConverter

class MarkdownConverter:
  _module_path = os.path.dirname(os.path.abspath(__file__))

  def __init__(self, url, parser):
    self.request_html(url)
    self.convert_html_bs4(parser)
    self.title = self.find_category_title()

  def request_html(self, url):
    res = requests.get(url)
    self.html = res.text

  def convert_html_bs4(self, parser):
    self.soup = BeautifulSoup(self.html, "lxml")

  def find_category_title(self):
    category_title_soup = self.soup.find("ol", "breadcrumb")
    _, _, tilte_soup = category_title_soup.find_all("li")

    title = tilte_soup.get_text()
    return title

if __name__ == "__main__":
  url = "https://school.programmers.co.kr/learn/courses/30/lessons/60057"
  if len(sys.argv) != 1:
    url = sys.argv[1]

  markdown_converter = MarkdownConverter(url, "lxml")
  print(markdown_converter.title)

참고한 레퍼런스

매주 생성해야 하는 주차별 폴더, 소스코드 파일 생성 자동화

이 코드는 셸 문법을 공부해가며 직접 구현했다. 역시 gpt 의 도움을 많이 받았다.

create.sh

#!/bin/bash
 
# 형식 : ./create.sh {이름} {확장자} -> 확장자를 전달하지 않으면 py 파일을 생성합니다.
# 위의 명령어를 실행하면 현재 주차에 해당하는 자신이 생성해야 할 파일과 폴더들을 생성합니다.

# 스크립트 파일이 위치한 디렉토리 경로
script_dir=$(dirname "$(readlink -f "$0")")

# 프로젝트의 루트 디렉토리 경로
root_dir=$(dirname "$script_dir")

# 현재 날짜의 주차 정보 가져오기
current_week=$((10#$(date +%U)))
study_week=$((current_week + 11))
week_folder="$root_dir/week$study_week"
mkdir -p "$week_folder"

file="$root_dir/scripts/problems.txt"
executor_name="$1"
expansion="$2"  # 두 번째 인자로 받은 확장자

# 파일을 한 줄씩 읽어 배열에 담기
IFS=$'\n' read -d '' -r -a lines < "$file"

# 배열 순회하여 각 줄 출력
total_boj=${#lines[@]}
length=$((total_boj - 1))

for (( i=0; i<${#lines[@]}; i++ )); do
  link="${lines[$i]}"
  
  # TODO: 19주차부터 지울 부분
  problem_number=$(echo $link | cut -d '/' -f5 )
  problem_title=$(python $script_dir/crawling.py $problem_number)
  problem_folder="${week_folder}/BOJ_${problem_number}"

  # # TODO: 19주차부터 PSG 에 적용
  # if [[ $i < $length ]]; then
  #   problem_number=$(echo $link | cut -d '/' -f5 )
  #   problem_title=$(python $script_dir/crawling.py $problem_number)
  #   problem_folder="${week_folder}/BOJ_${problem_number}"
  # else
  #   problem_title=$(python ./scripts/crawling_psg.py $link)
  #   problem_folder="${week_folder}/PSG_${problem_title}"
  # fi

  mkdir -p "$problem_folder"

  # 두 번째 인자가 주어졌을 때 해당 확장자로 파일 생성
  if [ -n "$expansion" ]; then
    touch "${problem_folder}/${problem_title}_${executor_name}.${expansion}"
  else
    # 두 번째 인자가 주어지지 않았을 때 기본적으로 py 확장자로 파일 생성
    touch "${problem_folder}/${problem_title}_${executor_name}.py"
  fi
done

스터디원들이 파이썬, 자바스크립트, c++ 로 코테를 준비하는 인원들로 나뉘었기에 각자의 언어별로 생성할 수 있도록 분기처리했다.

실제 구현 결과는 백준 알고리즘 스터디 깃허브 링크를 참고하자.

나는 개인적으로 이 스크립트가 굉장히 유용했다. 스터디장이다보니 매주 반복적으로 해야하는 작업들이 있는데 그 부분에 대한 공수가 많이 줄었다.

문제 할당 자동화하기

매주 스터디에 참여하는 인원들이 고정적이지 않다.
이게 큰 문제다.

스터디때마다 문제를 할당할때 7문제에 7개의 이름을 적절히 분배해서 사다리타기를 하는데 갑자기 인원이 해당 주차에 참여할 수 없게 된다면 어떤 문제를 누구에게 할당해야 할지 머리가 아프다.

그리고 그렇게 할당한 문제는 또 노션에 스크린샷을 찍어 공지해야 한다..

귀찮은 일들이다...

그래서 명령어 한줄이면 문제를 바로 할당할 수 있고 이걸 바로 깃허브 read.md 에 반영되도록 했다.

굉장히 편리했다.

assign.sh

#!/bin/bash

# 변수 설정
exclude_members=("$@")
all_members=("스터디원1" "스터디원2" ... "홍지훈")

# 인자로 전달한 인원을 멤버에서 제외
for member in "${exclude_members[@]}"; do
  all_members=("${all_members[@]//$member}")
done
all_members=($(echo "${all_members[@]}" | tr -s ' ' '\n' | grep -v '^$'))

# 인원 수 확인
num_members=${#all_members[@]}
num_problems=$((num_members > 2 ? 2 : num_members))

add_member=()

for ((i = 0; i < 7 - $num_members; i++)); do
  random_index=$((RANDOM % ${#all_members[@]}))
  add_member+=("${all_members[random_index]}")
done

all_members+=("${add_member[@]}")

# 스크립트 파일이 위치한 디렉토리 경로
script_dir=$(dirname "$(readlink -f "$0")")

# 프로젝트의 루트 디렉토리 경로
root_dir=$(dirname "$script_dir")

current_week=$((10#$(date +%U)))
study_week=$((current_week + 11))
file="$root_dir/scripts/problems.txt"

# 파일을 한 줄씩 읽어 배열에 담기
IFS=$'\n' read -d '' -r -a lines < "$file"

total_boj=${#lines[@]}
# length=$((total_boj - 1))
length=$((total_boj))

problems=()

# 배열 순회하여 각 줄 출력
for ((i = 0; i < ${#lines[@]}; i++)); do
  link=${lines[$i]}
  if [[ $i < $length ]]; then
    problem_number=$(echo $link | cut -d '/' -f5 )
    problem_title=$(python ./scripts/crawling.py $problem_number)
    problems+=("${problem_number}_${problem_title}")
  # else
  #   problem_title=$(python ./scripts/crawling_psg.py $link)
  #   problems+=("${problem_title}")
  fi
done

# # exclude_members가 비어있는 경우
# if [[ -z "$exclude_members" ]]; then
#   # 랜덤 숫자로 선택된 문제 추가
#   random_problem_index=$((RANDOM % 7))
#   problems+=("${problems[$random_problem_index]}")
# fi

# Fisher-Yates 알고리즘 -> 인원 랜덤 섞기
for i in "${!all_members[@]}"; do
  j=$((RANDOM % ($i + 1)))
  temp=${all_members[$i]}
  all_members[$i]=${all_members[$j]}
  all_members[$j]=$temp
done

# 결과 출력
echo "# $study_week 주차 문제 배정 결과" > weekly_assign.md
echo "" >> weekly_assign.md

# 문제의 수에 따라 표를 생성합니다.
printf "|" >> weekly_assign.md

for problem in "${problems[@]}"; do
  printf "%s" " $problem |" >> weekly_assign.md
done

echo "" >> weekly_assign.md

printf "|" >> weekly_assign.md

for problem in "${problems[@]}"; do
  printf "%s" " --- |" >> weekly_assign.md
done

echo "" >> weekly_assign.md

printf "|" >> weekly_assign.md

for member in "${all_members[@]}"; do
  printf "%s" " $member |" >> weekly_assign.md
done

매주 problems.txt 만 업데이트해주고 ./assign.sh 만 실행해주면 자동으로 적절히 각 스터디원들에게 문제를 할당하고 깃허브 리드미에 공지해준다.

이런식으로 셸을 일상속에 활용해 볼 수 있었다.

만약 반복적으로 하는 일들이 있다면 어지간한 것은 모두 자동화하는데 셸이나 파이썬을 사용하여 해결 할 수 있을 것 같다.

profile
미래의 나를 만들어나가는 한 개발자의 블로그입니다.

0개의 댓글