2022년이 되었으니 알고리즘 공부를 드디어 드디어 해야겠다고 다짐했다.
그래서 애초에 서로 모르는 사람들 끌어다가 스터디를 만들었다. 문제를 풀면 솔루션을 레포에 올리기로 했는데 우리가 총 몇 개의 문제랑 몇 개의 솔루션을 올렸는지 대문에 써놓으면 더 열심히할 것 같아서 시작하게 되었다.
목표
문제를 풀고 솔루션을 레포에 푸쉬할 때마다 총 문제 수와 해답 수 대문에 자동으로 띄우기.
최종적으로 아래와 같은 그림이 나오게된다.

어떻게?
Github Action을 사용했다. CI/CD를 github에서 자동으로 작업하게 해주는 툴인데 생각보다 편리하다. 그냥 .yml 파일에 해야될 일을 쭉 적으면 된다! 라고 생각하면 쉽겠지만 생각보다 화나게 한 점이 많았다. 후하

아래와 같이 진행된다.
- 총 문제수와 해답수를 세준다. 이는 python 스크립트를 이용한다.
- 위의 스크립트 결과를 shell script로 보낸 후 README를 수정한다.
- 이 script 파일을 Github Action으로 수행한다. 여기서 편집된 README를 add-commit-push까지 진행해준다.
문제수와 해답수 세주기
예전에는 os 라이브러리로 하나하나 설정했던 기억이 있는데... 이제는 os.walk로 이 작업을 간단하게 할 수 있다. 어떻게 하면 하위 디렉토리를 다 조회할까 고민했었는데 그냥 os.walk(path) 넣어주면 이 모든 게 가능하다. (참고: https://codechacha.com/ko/python-walk-files/)
우선 문제/솔루션의 구조는 아래와 같다.
Problems
├─ 01Jan
│ ├─ prob01
│ │ ├─ README.md
│ │ ├─ 1pha.py
│ │ └─ ghh.py
│ └─ prob02
│ └─ hss.py
├─ 02Feb
└─ 03March
├─ prob01
└─ prob02
월별로 폴더를 구분하고, 각 폴더 내에는 문제 별 폴더가 있다. 문제 별 폴더 안에는 문제 설명을 담은 markdown 파일과 그 외 솔루션 파일들이 있다. 언어는 자유롭게 하기로 했기 때문에 .cpp나 .py가 올 수 있다 (예시에는 없지만)
결과적으로 여기서 해야하는 건
- 문제 수 구하기 월별 폴더 내에 있는 폴더의 개수를 구하는 것과 같다.
- 솔루션 수 구하기 각 문제 폴더 내 .md 파일을 제외한 파일의 수를 구하는 것과 같다.
# count.py
import os
num_prob = 0
num_solution = 0
for root, dirs, files in os.walk("./Archives"):
# file = (root, dirs, files)
if len(root.split("/")) == 3:
# if current directory is month
num_prob += len(dirs)
if len(root.split("/")) == 4:
# if current directory is problem
solutions = list(filter(lambda x: not x.endswith(".md"), files))
num_solution += len(solutions)
print(num_prob, num_solution)
문제 디렉토리랑 솔루션 파일들을 구분하는 과정에서 조금 더 현명한 방법은 없을까 했는데 그냥 / 개수로... ㅋㅋ; 배포할 것도 아니고 대충하자! 이게 알고리즘 스터디장이 할 소리인지는 모르겠다.
어쨌든 위 파일을 count.py로 정리했다. 이 결과를 shell script로 보내서 사용하면 된다.
bash script로 넘겨주기
후 일단 여기서 첫 번째로 봉착한 문제는 python script의 결과를 어떻게 하면 shell script에서 사용할 수 있느냐이다. 당연히 스크립트에서 출력된 걸 그대로 pipe ( | )를 이용해서 넘겨주면 되는데 더 우아한(?) 방법이 없나 고민하다가 빨리 일해야해서 그냥 파이프 썼다 (ㅋ).
내가 hello_world.py 라는 파일을 통해 콘솔에 Hello World!를 출력했다고 가정해보자.
이를 bash에서 쓰고 싶으면 아래와 같이 작성하면 된다.
# hello_world.py
print("Hello World!")
#!/bin/bash
output=$(python3 count.py)
이러면 output에는 Hello World!라는 문자열이 출력된다.
일단 여기까지는 모두가 너무 잘 아는 사실이다.
그런데 나는 앞의 스크립트에서 두 개의 값을 전달 받아야한다 - 문제 수와 해답 수. 이거는 어떻게 처리하는가 고민했는데 bash에서도 배열을 사용할 수 있다는 걸 알았다. (참고: http://blog.redjini.com/282)
핵심적인건 결과 값에 앞뒤로 괄호를 추가하면 이를 그냥 배열로 받게 된다. 구분은 띄어쓰기로 된다. 그래서 내가 그냥 count.py에서 출력한 결과는 쉼표 없이 공백으로만 두 수가 뜨기 때문에 결과값 앞뒤에 괄호만 추가하면 된다. (어이 없어)
output=($(python3 count.py))
이제 이 결과를 가지고 문구를 만들어준다. 여기서 bash shell script에서 알아둘 점 몇 가지는 -
- 변수 선언에 띄어쓰기는 없어야한다.
- 변수를 사용하려면 앞에 $를 붙인다
- 문자열 내에 변수를 사용하고 싶으면, 선언할 때 큰 따옴표를 사용한다 (" ")
너무 티끌같은 정보지만 습관적으로 띄어쓰기하다가 왜 에러난지 몰랐다. 한 5분 동안 스턴 당했음.
그래서 내가 "총 5개의 문제와 17개의 해답이 업로드되었습니다." 라는 문구를 선언하고 싶으면 아래와 같이 작성한다. 여기서 5와 17은 앞의 python script의 결과를 받은 output array에 포함되어 있으니 아래와 같이 작성한다. 배열의 인덱스를 조회할 때 왜 아래와 같이 작성하는지 모르면 위의 링크를 참고하자.
statement="총 ${output[0]}개의 문제와 ${output[1]}개의 해답이 업로드되었습니다."
bash에서 Markdown 수정하기
정말 기본기가 부족하다 부족해! shell로 파일 수정하는게 뭐라고 한참 찾았다. 다양한 방법이 있겠지만 linux에 강력한 sed라는 명령어가 있다. 이 sed는 stream editor의 줄임말로 유닉스에서 텍스트 분해/변환을 하기 위한 프로그램이다. 사용법이 꽤나 간단하다. 사용법은 아래 링크를 참고하자. 정말 유용했다. (참고: https://jhnyang.tistory.com/287)
각설하고 내가 여기서 사용해야하는 기능은
- markdown에서 세 번째 줄에 항상 저 문구를 넣을 것이다. 그러니까 세 번째 줄을 지우고
- 세 번째 줄에 원하는 문구를 다시 추가하는 것이다.
sed '3d' README.md | sed "2a\\${statement}" >> NEW.md
rm README.md
mv NEW.md
위 글을 읽어보면 알겠지만 기본적으로 sed는 콘솔에 출력을 한다 (저장 안해줌) 그러니까 이 값은 계속 파이프로 던지고 저장하는 방식으로 파일을 수정해야 한다. 여기서 문구를 추가할 때 주의점은 반드시 큰 따옴표를 사용해야 위에서 선언한 변수를 사용할 수 있다. 나 왜 이거 자꾸 멍청하게 ${statement}가 나오나 한참을 찾아봤다. 따옴표... 죽여버리겠어. 왜 이렇게 bash를 못쓰냐구요? 원래 Windows 써가지고 .bat 파일을 썼거든요...
분명히 세 번째 줄을 지우고 교체하는 명령어가 있을 것 같은데 일단 난 수행 못했다 (멍청해). 그래서 세 번째 줄을 지우고, 그 결과값을 파이프로 보내서 여기에서 두 번째 줄 끝에 내가 원하는 문구를 추가했다. 그리고 이 출력값을 markdown으로 저장했다. 기본적으로 > README.md 를 하면 그냥 덮어씌워져야하는데 자꾸 README.md가 죽어버려서 그냥 새로운 NEW.md에 파일을 작성했다. 그리고 기존 README를 지우고 NEW의 이름을 바꿔버렸다 (완전 짜쳐버렸음)
어쨌든 이렇게 markdown을 수정했다.
Github Action으로 git 서버에서 위 과정 수행하기
Github Action은 내가 특정 Action에서 일련의 명령어를 수행해준다. 이 명령어에는 내가 지금 수행하려는 파일 수정도 포함되고 혹은 unittest 같이 코드 검증도 진행할 수 있다. 그리고 이 과정은 모두 github 서버에서 수행된다는 점이다. 즉 위에서 만든 .sh 파일을 github action에서 수행해야한다.
이게 돌아가는 방식은 python-app.yml이라는 .github/workflows/ 디렉토리 내의 파일을 통해 수행되는 것이다. 기본적인 사용방법은 굳이 하나하나 설명하지 말고 직접 사용해보는 것이 이해가 훨씬 빠르다. 이 yml 파일을 처음 부터 작성할 필요 없다. 이미 수많은 개발자들이 자기 입맛에 맞게 템플릿을 작성해놨다. 이걸 가져다 쓰는 건 검색해서 쓰면 된다.

레포에 가면 4번째 탭에 Actions가 있다. 이거 눌러

그럼 이렇게 수많은 템플릿이 있다. 우리의 경우는 그냥 Python Application을 누르면 된다. 쓰고싶은 Template의 Configure를 누르면 아래와 같이 .yml 파일을 수정할 수 있게 나온다.

주요 내용을 보면
- on 언제 이 workflow를 가동시킬지 지정한다. 그냥 main branch에 push/pull-request 쏠 때 자동하도록 디폴트로 정해져있다.
- 여기서 paths, paths-ignore를 추가해서 특정 경로에 있는 파일이 수정될 때만 Github Actions가 돌아가게 할 수 있다.
- jobs 어떤 일련의 작업을 수행할지 정해준다. 기본적으로 build가 있던데 이 외에는 뭐가 있는지 나도 살펴봐야 안다. 이 안에 하고 싶은 작업들을 넣으면 된다.
- runs-on 어떤 os에서 작업할지 지정한다.
- steps 이제 작업을 넣는다. 이 구분은 - 로 한다. -는 yaml 파일에서 리스트의 구성요소를 뜻한다.
- runs 수행할 shell script를 적어준다. 우리의 경우 update_md.sh를 돌리니까 bash update_md.sh를 사용한다. 혹시 windows powershell이나 다른 bash를 사용한다면 bash 외에 다른 명령을 주면 된다. (무의식적으로 pwsh 썼다가 계속 에러나서 화났다. cmdlet 관련 에러가 뜨면 windows shell 써놓은 거니까 참고하세요 ...)
- uses 이미 남들이 만들어놓은 runs를 불러다 쓰는 것이다. 오른쪽에서 검색하면 내가 필요한 명령어가 이미 있는지 확인할 수 있다. 그러면 굳이 runs 아래 명령어를 줄줄이 쓸 필요 없이 uses 옆의 이름만 쓰면 된다.
- name은 이 작업이 돌아갈 때 어떤 이름을 띄워줄 지 정해준다.
- with, shell 과 같이 python 버전이나 어떤 shell을 사용할지 지정해줄 수 있다. Github secrets 같은 변수들도 가져와서 사용할 수 있다. (e.g. ${{ secrets.HOST }} 와 같이 불러서 변수처럼 쓸 수 있음)
그 외의 내용은 Documents에 굉장히 잘 정리되어 있으니 참고하면 좋다.
Building and testing Node.js or Python - GitHub Docs
With GitHub Actions, you can run tests with tox and spread the work across multiple jobs. You'll need to invoke tox using the -e py option to choose the version of Python in your PATH, rather than specifying a specific version. For more information, see to
docs.github.com
우리의 경우 어떻게 진행하면 될까?
- .sh를 돌리기 위해 위에서 만든 shell script에 실행권한을 부여한다.
- .sh를 돌린다
- add-commit-push 하기 위해 사용자와 이메일을 추가한다.
- github server에서 수정된 .md를 add-commit-push 한다.
위 작업을 수행하는 것이 아래 python-app.yml 코드이다. 중간에 names: Update README.md 부터 보면 된다. 현재 최상단에 이 파일들을 넣는 것이 적절해보이지 않아서 .autoupdate/라는 디렉토리 안에 쑤셔넣었다. 사실 별 것 없는 명령어들이다.
# This workflow will install Python dependencies, run tests and lint with a single version of Python
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions
name: Python application
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up Python 3.10
uses: actions/setup-python@v2
with:
python-version: "3.10"
- name: Update README.md
shell: bash
run: |
chmod +x .autoupdate/update_md.sh
bash .autoupdate/update_md.sh
- name: Commit README.md
run: |
git config --global user.email "1phanatasmas@korea.ac.kr"
git config --global user.name "1pha"
git add README.md
git commit -m "<Action> auto update README.md"
git push origin main
앞으로 main branch에 push할 때마다 위에 있는 Github Action이 위잉 돌아갈 것이다. 여태까지 돌아간 workflows들을 확인하고 싶으면 다시 Actions 탭을 누른다.

- 체크 표시가 뜨면 잘 돌아간 것이다. 근데 종종 에러나도 체크 표시 뜰 때 있다. (읭)
- 노란불이 뜨면 돌아가는 중인 거다
- 빨간색 X표시가 뜨면 돌다 망한 것이다. 별 것 없는 코드라 에러는 안 날 것 같다.
휴 이렇게 자동화를 완성했다. 하지만 할 일이 태산이다.
#TODO
진짜 너무 대충 만들어서 수정할 거 산더미다. 일단 돌아가게만 만든 코드다보니...
- 귀찮아서 저 푸쉬는 모두 내가하는 것으로 되어있다. 내가 만들었으니까 내가 해도 되지 않나? 할 수 있지만 github id/email을 어떤 사용자가 push했느냐에 따라 다르게 push되게 만들어야할 것 같다.
- 올려놓은 솔루션이 valid한지 체크하는 것...도 하고 싶지만 이건 힘들 것 같다.
- 문제수/해답수만 푸쉬하는 것이 아니라 다른 것들도 업로드할 수 있을 것 같다. 어떤 사람이 몇 개의 문제를 풀었는지 살펴보는 것 같이 말이다.
- 그 밖에 마이너한 것들
- .sh에서 > 왜 안 먹는지 알아내야한다
- 파일 수 세는 거 맘에 안 든다. 누구는 5분 동안 짜도 멋있게 짜는데 난 왜 같은 5분 고민했는데 멍청해보이냐.
- python-app.yml도 뭔가 맘에 안든다. 사용자 추가도 저렇게 하는 게 맞는지 모르겠다.
- 예외처리들
- 종종 문제를 수정하지 않으면 굳이 push 하지 않아도 된다. 이런 경우에는 굳이 수정 update를 표시할 필요가 없다. → 문제 수정이 일어나는 Problems/ 디렉토리에 변화가 생길 때만 Github Action이 돌아가게 할 수 있었다. 이건 해결!
그래도 돌아가게 했으니까 알고리즘 공부 열심히하겠지? 후. 근데 연구보다 재밌어서 짜증나네.
참고
- Convert a Python Data list to a bash array
- [shell script] 배열(Array) 사용하기
- Python - os.walk()를 사용하여 디렉토리, 파일 탐색
- [리눅스/유닉스] 유용 명령어 sed를 살펴보자!
- SED 명령어 사용법
- Bash Shell Script - 변수 선언, 할당
- sed 명령어에 변수 사용하기
- [Linux] Redirect('>')와 Pipe('|')의 차이
- Can GitHub Actions directly edit files in a repository?
- A GitHub Action that automatically generates & updates markdown content (like your README.md) from external or remote files.
- Generate & Update Markdown Content
- run a bash script located in public folder github actions
- [2] Github Action 테스트 (bash command)
오늘도 티끌 끝
'_SANDBOX' 카테고리의 다른 글
Python Logging 사용하기: 나도 기록 좀 잘해보자 (0) | 2022.01.13 |
---|---|
맥북 세팅하기 (0) | 2022.01.09 |