11  파일(file)과 텍스트(text)

11.1 파일(file), 파일시스템(filesystem), os 모듈

  • 파일(file)
    프로그램이 읽거나 쓸 수 있는 텍스트 또는 바이트 스트림을 말한다(A file is a stream of text or byes that a program can read and/or write).
  • 파일시스템(filesystem)
    하나의 컴퓨터에서 파일들의 위계적 저장소
  • 경로(path)
    파일시스템 안에 있는 파일 또는 디렉터리의 위치를 표현하는 문자열
    • 상대 경로(relative path): 현재 워킹 디렉터리(current workding directory)에서 시작
      • ../data/zen.txt
    • 절대 경로(absolute path): 루트(Root) 디렉터리에서 시작
      • /Users/seokbumko/Learning/with-huckjae
    • 디렉터리 Separator: Unix-like system에서는 /, Windows에서는 \(파이썬 string에서는 \\로 이스케이핑 필요)
  • 루트(root) 디렉터리
    최상위 디렉터리
    • Unix-like system: /로 표시
    • Windows system: 드라이브 이름으로 표시, C:\
  • 홈(home) 디렉터리
    컴퓨터에서 개별 사용자를 위해 마련된 디렉터리
    • 터미널을 실행하면 보통 홈 디렉터리에서 시작된다.
    • Unix 계열 컴퓨터에서는 ~로 표시하고, 해당 환경변수는 HOME 임, 보통 /Users/username
    • 윈도우 컴퓨터에서는 USERPROFIlE 환경 변수를 사용, 보통 C:\Users\username

파이썬 os 모듈에 현재 파일시스템과 관련된 정보와 파일시스템을 다루는 다양한 함수들이 들어 있다.

11.2 PATH 환경변수(environment variable)(GitHub Copilot)

  • 일반 명사 path와 환경변수 PATH는 다른 개념이다.

The environment variable PATH is a crucial component in operating systems like Unix, Linux, and Windows. It specifies a set of directories where executable programs are located. When you type a command in the terminal or command prompt, the system searches through these directories to find the executable file associated with that command.

11.2.1 Purpose of PATH

  • Command Execution: When you run a command, the system searches the directories listed in the PATH variable to find the executable file.
  • Convenience: It allows you to run programs and scripts from any location without needing to specify their full path.

11.2.2 Structure of PATH

  • Unix-like Systems: Directories are separated by colons (:).
    • Example: /usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin
  • Windows Systems: Directories are separated by semicolons (;).
    • Example: C:\Windows\System32;C:\Windows;C:\Program Files\Python39

11.2.3 Viewing and Modifying PATH

You can view and modify the PATH environment variable using the terminal or command prompt.

11.2.3.1 Viewing PATH

  • Unix-like Systems:

    echo $PATH
  • Windows Systems:

    echo %PATH%

11.2.3.2 Modifying PATH

  • Unix-like Systems:

    # Temporarily add a directory to PATH
    export PATH=$PATH:/new/directory
    
    # Permanently add a directory to PATH (add to ~/.bashrc or ~/.bash_profile)
    echo 'export PATH=$PATH:/new/directory' >> ~/.bashrc
    source ~/.bashrc
  • Windows Systems:

    # Temporarily add a directory to PATH
    set PATH=%PATH%;C:\new\directory
    
    # Permanently add a directory to PATH (through System Properties)

11.2.4 Example in Python

You can access and modify the PATH environment variable in Python using the os module.

11.2.4.1 Accessing PATH

11.2.4.2 Modifying PATH

11.2.5 Summary

  • PATH Environment Variable: Specifies directories where executable programs are located.
  • Purpose: Allows the system to find and execute programs without specifying their full path.
  • Structure: Uses colons (:) on Unix-like systems and semicolons (;) on Windows.
  • Viewing and Modifying: Can be done through the terminal, command prompt, or programmatically in Python.

Understanding and managing the PATH environment variable is essential for configuring your system to run programs and scripts efficiently.

11.3 os.path 모듈과 pathlib 모듈

이전에는 파일시스템을 다룰 때 os 모듈의 서브 모듈인 os.path을 많이 사용했었는데, 이제는 파일시스템에 관한 객체 지향형 접근법을 적용한 pathlib 모듈을 더 선호하는 경향이 있다. 그래서 pathlib 모듈을 중심으로 공부하면서 부가적으로 os.path를 사용할 때는 어떻게 하는지 정리하고자 한다.

문자열로 파일 경로 다루기

파일 경로가 문자열로 되어 있기 때문에 문자열 그대로 사용하면 안 될까하는 의문을 가질 수도 있는 데, 여러 가지 문제가 있을 수 있다. 무엇보다 파일 경로를 표시하는 방법이 윈도우와 Unix 계열 시스템이 서로 다른 점이 큰 문제다. 따라서 문자열을 이용하는 경우, 윈도우 시스템에서 작성한 코드가 맥오에스에서는 작동하지 않을 수 있다. 어느 플랫폼에서든 사용할 수 있는 인터페이스를 가지면서, 플랫폼에 따라서 필요한 내용들을 자동적으로 맞춰주는 도구가 필요하다. 그리고 파일 경로를 문자열 다루는 방식으로 처리하다가 보면 에러가 발생할 확률이 높아지고, 가독성도 떨어진다.

11.3.1 pathlib.Path 클래스

pathlib은 파일시스템에 대한 객체 지향형 접근법을 취하는데, 가장 중요한 역할을 하는 것이 pathlib.Path 클래스이다. 다음 예를 보자.

from pathlib import Path
cwd = Path.cwd() # absolute, cwd
cwd2 = Path(".")  # relative, cwd
home = Path.home()
cwd, cwd2, home
(PosixPath('/Users/seokbumko/Learning/Education/cds-python'),
 PosixPath('.'),
 PosixPath('/Users/seokbumko'))

Path.cwd()는 현재 워킹 디렉터리를 의미하는 Path 객체를 반환하는 클래스 메서드이고, Path.home()은 홈 디렉터리를 의미하는 Path 객체를 반환하는 클래스 메서드이다.

이렇게 특정 역할을 하는 디렉터리 대신 임의의 파일에 대한 경로를 나타내는 문자열을 가지고도 Path 객체를 만들 수 있다. 다음은 현재 디렉터리에 있는 data 서브디렉터리에 들어 있는 zen.txt 파일을 가리키는 Path 객체를 만들어 본 예이다.

my_file = "./data/zen.txt"
my_file_path = Path(my_file)
my_file_path
PosixPath('data/zen.txt')

pathlib 모듈은 디렉터리를 조작을 편리하게 해 주는 /라는 연산자를 제공한다. 이 연산자를 사용하여 다음과 같이 해서 위 my_file_path와 동일한 경로를 만들 수 있다.

my_file_path2 = Path("data") / "zen.txt"
my_file_path2
PosixPath('data/zen.txt')

또는 Path() 클래스 호출을 할 때, 경로를 이루는 요소들을 개별 인자로 줘서 경로를 만들 수 있다.

my_file_path3 = Path("data", "zen.txt")
my_file_path3
PosixPath('data/zen.txt')

my_file_path 등은 모두 상대 경로이다. Path 객체에 is_absolute() 메서드를 적용하여 절대 경로인지 아닌지 물어볼 수 있다.

my_file_path.is_absolute(), my_file_path2.is_absolute(), my_file_path3.is_absolute()
(False, False, False)

이것을 절대 경로로 바꾸고자 할 때는 “현재 디렉터리”를 의미하는 Path.cwd() 클래스 메서드를 앞에 붙여 계산함할 수 있다.

new_path = Path.cwd() / my_file_path
print(new_path)
print(new_path.is_absolute())
/Users/seokbumko/Learning/Education/cds-python/data/zen.txt
True

11.3.1.1 파일 경로를 구성 요소로 분리하기

다음과 같은 파일에 대한 경로가 Path 객체로 정의되었다고 가정해 보자.

다음과 같은 Path 객체의 속성값을 사용하여 경로의 부분을 알아낼 수 있다.

os.path 모듈의 basename() 메서드는 Path 객체의 name 속성과 같은 값인 파일 이름을 반환한다.

os.path 모듈로 확장자를 확인할 때는 os.path.splitext()(철자 주의: t가 하나이다)를 사용하는데, 이 함수는 튜틀을 반환하고, 첫 번째 아이템은 확장자 이전의 값들, 두 번째 아이템이 확장자이다.

11.3.1.2 디렉터리에 있는 파일, 디렉터리 이름 확인하기

디렉터리에는 파일, (서브) 디렉터리가 들어 있다. 그 이름들을 알 필요가 있는 경우가 종종 발생한다.

  • Path 객체가 디렉터리를 가리키는 경우, 이 객체의 iterdir() 메서드는 디렉터리 콘텐트를 yield 할 수 있는 iterator를 반환한다.
  • os 모듈의 listdir() 메서드는 디렉터리 콘텐츠를 모아서 하나의 파이썬 리스트로 반환한다. 물론 위 iterdir()로 반환되는 iteratorlist() 함수로 보내도 같은 결과를 얻는다.
for f in Path(".").iterdir():
  print(f)
function.qmd
jupyter.qmd
.Rhistory
sequence.qmd
grouping.qmd
_quarto.yml
sequence.html
_publish.yml
file-text.qmd
list-tuple.html
index.html
.DS_Store
_language-ko.yml
iterator-generator.qmd
jupyter.html
date-time.qmd
intro.qmd
file-text.quarto_ipynb
images
references.bib
python-setting.html
module-package.qmd
python-core.html
class.qmd
loop-condition.html
match-object.qmd
ipython_display_example.py
jupyter_cache
site_libs
mapping.html
object.html
__pycache__
python-core.qmd
index.qmd
regex.qmd
string.html
intro.html
generator.qmd
loop-condition.qmd
_extensions
cds-python.Rproj
.gitignore
lookaround.qmd
.venv
setting.qmd
cover.png
list-tuple.qmd
_book
python-setting.qmd
ai-assistants.qmd
setting.html
module.py
references.qmd
python-setting_cache
object.qmd
.git
data
string.qmd
.Rproj.user
.quarto
function.html
mapping.qmd
import os
os.listdir(".")
# list(Path(".").iterdir()_
['function.qmd',
 'jupyter.qmd',
 '.Rhistory',
 'sequence.qmd',
 'grouping.qmd',
 '_quarto.yml',
 'sequence.html',
 '_publish.yml',
 'file-text.qmd',
 'list-tuple.html',
 'index.html',
 '.DS_Store',
 '_language-ko.yml',
 'iterator-generator.qmd',
 'jupyter.html',
 'date-time.qmd',
 'intro.qmd',
 'file-text.quarto_ipynb',
 'images',
 'references.bib',
 'python-setting.html',
 'module-package.qmd',
 'python-core.html',
 'class.qmd',
 'loop-condition.html',
 'match-object.qmd',
 'ipython_display_example.py',
 'jupyter_cache',
 'site_libs',
 'mapping.html',
 'object.html',
 '__pycache__',
 'python-core.qmd',
 'index.qmd',
 'regex.qmd',
 'string.html',
 'intro.html',
 'generator.qmd',
 'loop-condition.qmd',
 '_extensions',
 'cds-python.Rproj',
 '.gitignore',
 'lookaround.qmd',
 '.venv',
 'setting.qmd',
 'cover.png',
 'list-tuple.qmd',
 '_book',
 'python-setting.qmd',
 'ai-assistants.qmd',
 'setting.html',
 'module.py',
 'references.qmd',
 'python-setting_cache',
 'object.qmd',
 '.git',
 'data',
 'string.qmd',
 '.Rproj.user',
 '.quarto',
 'function.html',
 'mapping.qmd']

11.3.1.3 디렉터리에 있는 파일, 디렉터리를 필터링하기

앞의 pahtlib.Path.iterdir() 함수는 디렉터리의 모든 콘텐츠를 보여 주는데, 어떤 경우는 특정 이름이나 확장자를 가진 파일만 가지고 올 필요가 있다. 이 경우 사용할 수 있는 메서드가 .glob()이다. 이 메서드 안에서 터미널에서 사용하는 globbing 패턴을 사용할 수 있다. 흔히 사용되는 글로빙 패턴은 다음과 같다.

  1. 별표(*): 0개 또는 그 이상의 문자
  2. 물음표(?): 하나의 문자
  3. 대괄호([]): 이 안에 포함된 문자들 가운데 하나. file[1-3]file1, file2, file3와 매칭할 수 있다.
for f in Path(".").glob("*.qmd"): # 확장자가 .qmd인 파일들 
  print(f)
function.qmd
jupyter.qmd
sequence.qmd
grouping.qmd
file-text.qmd
iterator-generator.qmd
date-time.qmd
intro.qmd
module-package.qmd
class.qmd
match-object.qmd
python-core.qmd
index.qmd
regex.qmd
generator.qmd
loop-condition.qmd
lookaround.qmd
setting.qmd
list-tuple.qmd
python-setting.qmd
ai-assistants.qmd
references.qmd
object.qmd
string.qmd
mapping.qmd
for f in Path(".").glob("s*.*"): # s로 시작하는 파일 
  print(f)
sequence.qmd
sequence.html
string.html
setting.qmd
setting.html
string.qmd
for f in Path(".").glob("*.?m?"): # 3 글자로 된 확장자를 가지면서 가운데가 m인 파일  
  print(f)
function.qmd
jupyter.qmd
sequence.qmd
grouping.qmd
_quarto.yml
_publish.yml
file-text.qmd
_language-ko.yml
iterator-generator.qmd
date-time.qmd
intro.qmd
module-package.qmd
class.qmd
match-object.qmd
python-core.qmd
index.qmd
regex.qmd
generator.qmd
loop-condition.qmd
lookaround.qmd
setting.qmd
list-tuple.qmd
python-setting.qmd
ai-assistants.qmd
references.qmd
object.qmd
string.qmd
mapping.qmd

11.3.1.4 확인: 존재하는가?, 디렉터리인가/파일인가?

경로의 존재 여부는 .exists() 메서드로 확인하고, 그것이 파일인지는 .is_file(), 디렉터리인지는 .is_dir() 메서드로 확인할 수 있다.

from pathlib import Path

# Define the path
path = Path("/Users/seokbumko/Learning/with-huckjae/file-text.qmd")

# Check if the path exists
if path.exists():
    print(f"The path {path} exists.")
    # Check if it is a file
    if path.is_file():
        print(f"The path {path} is a file.")
    # Check if it is a directory
    elif path.is_dir():
        print(f"The path {path} is a directory.")
else:
    print(f"The path {path} does not exist.")
The path /Users/seokbumko/Learning/with-huckjae/file-text.qmd does not exist.

11.4 파이썬으로 파일 읽기/쓰기

다음은 open() 함수를 사용하여, data/zen.txt이라는 텍스트 파일을 읽는 코드이다. 이 코드 패턴은 암기하는 것이 좋다.

위 코드는 몇 줄 안 되지만 이야깃거리가 많다. 먼저 open() 함수부터 살펴보자.

11.4.1 open() 함수

open() 함수의 도움말을 이해하기 위해서는 사전 지식이 많이 필요하다. 하나씩 배워가면서 open() 함수의 도움말을 자주 읽어보면 좋다.

여기서 논하는 파일 객체는, 즉 파이썬 언어가 말하는 파일 객체(file object)는 실제 컴퓨터에 이미 저장된 파일이라기 보다는 그것을 대리하는 파이썬 객체라는 것을 알 필요가 있다. 파일의 종류가 다양하듯이 그것을 대리하는 파일 객체의 타입도 다양하다.

파이썬은 파일의 콘텐츠가 text인지, raw data인 bytes인지를 명확히 구분한다(사용자도 반드시 명확히 구분해서 사용해야 한다).

open() 함수는 파일 객체(file object)를 반환한다. 그런데 사용자가 지정하는 파라미터의 조합에 따라 (겉으로 보이진 않지만) 서로 다른 클래스를 가진 파일 객체를 반환한다. 여기서 open() 함수는 일종의 factory function이다.

위 코드 with open('data/zen.txt', 'rt') as file: 행은 zen.txt이라는 실제 파일을 텍스트 읽기(rt) 모드로 열고 그 파일을 대리하는 파일 객체를 file이라는 이름의 별칭으로 대치한다. 그리고 파이썬은 io 모듈을 통해 파일을 다루는 기본 기능을 제공한다(이 모듈을 직접 import 하는 경우는 드물지만 뒤에서는 이 모듈이 작동하고 있음을 알고 있는 것이 좋다). 핵심적인 역할을 하는 open() 함수 역시 실제로는 io.open() 함수의 별칭이다.

다음은 data/zen.txt 텍스트 파일과 dat/python-logo.png라는 바이너리 파일을 열고, 그것을 대리하는 file 객체의 attributes를 확인해 본 예이다(일단 파일 안의 콘텐츠는 무시했다).

with open('data/zen.txt', 'rt') as file:
    print(type(file))
    print(file.mode)
    print(file.name)
    print(file.encoding)
<class '_io.TextIOWrapper'>
rt
data/zen.txt
UTF-8
with open('data/python-logo.png', 'rb') as file:
    print(type(file))
    print(file.mode)
    print(file.name)
<class '_io.BufferedReader'>
rb
data/python-logo.png

open() 파일의 mode 파라미터는 파일을 여는 방법을 결정한다. rreading을 의미한다. t는 콘텐츠가 text임을 의미하고, b는 콘텐츠가 bytes임을 의미한다. file 객체는 이 경우 텍스트인 경우 TextIOWrapper, 바이너리인 경우 BufferedReader 타입이다. 그리고 파일 객체는 mode, name, 텍스트 모드인 경우 encoding이라는 속성을 가지고 있음을 볼 수 있다.

11.4.2 텍스트 콘텐츠를 읽기

텍스트 읽기 모드(mode='rt')인 파라미터를 사용하여 반환되는 파일 객체는 다음과 같은 읽기용 메서드를 제공한다.

  • f.read(): 다른 파라미터를 사용하지 않는 경우, 콘텐츠를 하나의 문자열로 반환
  • f.readline(): 파라미터를 사용하지 않는 경우, 다음 한 줄을 반환(newline 포함)
  • f.readlines(): 텍스트 콘텐츠 첫 행에서 끝 행까지로 이뤄진, 하나의 파이썬 리스트를 반환. 하나의 행이 리스트의 하나의 아이템이 됨. f