# 檔案系統操作
###### tags: `python`
## 目錄與檔案路徑
#### 路徑的表達方式
- 相對路徑:指相對於目前目錄的路徑,目前的目錄稱又稱為工作目錄
例如:`example`目錄為目前目錄下有一個子目錄為`example`
- 絕對路徑:由系統的根目錄開始表達,Linux和Windows表達方式有其差異
Linux: `/etc/httpd/config`
Windows: `C:\Users\Administrator\Documents\File.txt`
- `.`:特殊目錄符號,代表目前的目錄
- `..`:特殊目錄符號,代表上一層目錄
#### `os`模組與`os.path`模組
Python內與目錄相關的模組為`os`與`os.path`兩個模組,因為`os.path`為`os`內的子模組,所以只需要`import os`即可。
##### 取得路徑
```python=
import os
print(os.getcwd()) # 取得目前工作目錄
print(os.path.abspath('.')) # 取得目前目錄的絕對路徑
print(os.path.abspath('..')) # 取得目前目錄上一層目錄的絕對路徑
print(os.path.abspath('hello.txt')) # 取得目前目錄下指定檔案的絕對路徑
print(os.path.relpath('D:\\')) # 回傳目前目錄到D:\的相對路徑
print(os.path.relpath('D:\\Downloads')) # 回傳目前目錄到D:\Downloads的相對路徑
print(os.path.relpath('D:\\Downloads', 'hello.txt')) # 回傳目前目錄下hello.txt檔案到D:\Downloads的相對路徑
```
> **備註:**
>
> 請記得在字串內,「\」字元需要使用轉譯符號,否則會被當成轉譯字元。
##### 檢查路徑
```python=
import os
print(os.path.exist('hello.txt')) # 檢查目前目錄下是否存在hello.txt檔案
print(os.path.isabs('D:\\Documents\\hello.txt')) # 檢查路徑是否為一個絕對路徑
print(os.path.isdir('repo')) # 檢查路徑是否為目錄
print(os.path.isfile('repo')) # 檢查路徑是否為檔案
```
說明:
路徑檢查相關函式回傳直皆為:`True`=存在、`False`=不存在
#### 檔案與目錄操作
```python=
import os
os.path.mkdir('sub') # 在目前的目錄下建立sub目錄
os.path.rmdir('sub') # 刪除目前目錄下的sub目錄
os.path.remove('file.txt') # 刪除目前目錄下的file.txt檔案
os.path.chdir('sub') # 將目前的工作目錄切換到sub目錄下
```
> **補充:**
>
> 建議在進行目錄操作前,先呼叫`os.path.exists()`檢查是否存在,否則可能會因為建立一個已經存在的目錄而產生例外結束程式。
#### 目錄與檔案的屬性與內容
```
import os
os.path.getsize('hello.txt') # 取得hello.txt檔案大小(單位為bytes)
os.listdir() # 列出目前目錄下的內容
os.listdir('D:\\Downloads') # 列出D:\Download目錄下的內容
```
#### 目錄與檔案的屬性與內容(使用glob模組)
這個模組與os模組最大的差別是可以使用萬用字元「*」與「?」
```python=
import glob
print(glob.glob('D:\\Download\\*.txt')) # 列出D:\Downloads目錄下所有附檔名為txt的檔案
print(glob.glob('D:\\Download\\?.txt')) # 列出D:\Downloads目錄下所有附檔名為txt,且主檔名只有一個字元的檔案
```
#### 列出目錄以及目錄下所有子目錄內容
```
import os
for dir_name, sub_dir_names, filenames in os.walk():
print(dir_name) # 目前工作目錄名稱
print(sub_dir_names) # 目前目錄下所有子目錄名稱
print(file_names) # 目前目錄下所有檔案名稱
```
## 讀寫檔案
#### 開啟檔案
所有檔案操作都必須先透過`open()`函式開啟檔案並取得檔案物件後才能進行檔案操作。
檔案開啟模式:
| 模式 | 描述 |
| :--- | :----------------------------------------------------------- |
| t | 文字模式 (預設)。 |
| x | 寫模式,新建一個檔案,如果該檔案已存在則會報錯。 |
| b | 二進位制模式。 |
| + | 開啟一個檔案進行更新(可讀可寫)。 |
| r | 以只讀方式開啟檔案。檔案的指標將會放在檔案的開頭。這是預設模式。 |
| rb | 以二進位制格式開啟一個檔案用於只讀。檔案指標將會放在檔案的開頭。這是預設模式。一般用於非文字檔案如圖片等。 |
| r+ | 開啟一個檔案用於讀寫。檔案指標將會放在檔案的開頭。 |
| rb+ | 以二進位制格式開啟一個檔案用於讀寫。檔案指標將會放在檔案的開頭。一般用於非文字檔案如圖片等。 |
| w | 開啟一個檔案只用於寫入。如果該檔案已存在則開啟檔案,並從開頭開始編輯,即原有內容會被刪除。如果該檔案不存在,建立新檔案。 |
| wb | 以二進位制格式開啟一個檔案只用於寫入。如果該檔案已存在則開啟檔案,並從開頭開始編輯,即原有內容會被刪除。如果該檔案不存在,建立新檔案。一般用於非文字檔案如圖片等。 |
| w+ | 開啟一個檔案用於讀寫。如果該檔案已存在則開啟檔案,並從開頭開始編輯,即原有內容會被刪除。如果該檔案不存在,建立新檔案。 |
| wb+ | 以二進位制格式開啟一個檔案用於讀寫。如果該檔案已存在則開啟檔案,並從開頭開始編輯,即原有內容會被刪除。如果該檔案不存在,建立新檔案。一般用於非文字檔案如圖片等。 |
| a | 開啟一個檔案用於追加。如果該檔案已存在,檔案指標將會放在檔案的結尾。也就是說,新的內容將會被寫入到已有內容之後。如果該檔案不存在,建立新檔案進行寫入。 |
| ab | 以二進位制格式開啟一個檔案用於追加。如果該檔案已存在,檔案指標將會放在檔案的結尾。也就是說,新的內容將會被寫入到已有內容之後。如果該檔案不存在,建立新檔案進行寫入。 |
| a+ | 開啟一個檔案用於讀寫。如果該檔案已存在,檔案指標將會放在檔案的結尾。檔案開啟時會是追加模式。如果該檔案不存在,建立新檔案用於讀寫。 |
| ab+ | 以二進位制格式開啟一個檔案用於追加。如果該檔案已存在,檔案指標將會放在檔案的結尾。如果該檔案不存在,建立新檔案用於讀寫。 |
```python=
with open('test.txt') as file: # 開啟目前目錄下的test.txt檔案並
psss
```
>
>
> **補充:**
>
> 開啟檔案時可以相對路徑或絕對路徑指定檔案位置。
#### 檔案編碼
若沒有指定encoding參數,開檔時將採用 `locale.getpreferredencoding()` 的編碼設定,如果要強制指定使用UTF-8編碼,可以加上`encoding`參數來指定編碼,例如:
```python=
open('text.txt', encoding='uff-8')
```
#### 讀取檔案
##### ㄧ次讀取全部檔案內容
```python=
with open('test.txt') as file:
content = file.read() # 一次讀取全部檔案內容
print(content)
```
##### 逐行讀取檔案內容
```python=
with open('test.txt') as file:
for line in file:
print(line)
```
另一種方式(使用`readlines()`方法)
```python=
with open('test.txt') as file:
lines = file.readlines()
for line in lines:
print(line)
```
#### 寫入檔案
當呼叫`open()`函式打開檔案時,預設只能讀取內容而無法寫入,因此如果要寫入內容到檔案,需要指定「w」模式才能人入資料到檔案。
##### 開啟檔案為寫入模式(會清空原檔案內容)
```python=
with open('test.txt', 'w') as file:
pass
```
##### 開啟檔案為寫入模式(檔案內容會附加到原始檔案後面)
```python=
with open('test.txt', 'a') as file:
pass
```
##### 寫入字串到檔案
```python=
with open('test.txt', 'w') as file:
file.write('Hello')
file.write(' Python')
```
> **補充:**
>
> 如果要寫入多行資料,需要加上「`\n`」來換行
## 檔案操作
#### shutil模組
Python提供了shutile用來操作檔案的複製、刪除、移動與修改名稱等等操作,要進行這些操作前需要引入shutil模組:
```python=
import shutil
```
#### 檔案複製
```python=
import shutil
shutil.copy('a.txt', 'b.txt') # 將目前目錄下的a.txt檔案複製為b.txt
```
#### 目錄複製
```python=
import shutil
shutil.copytree('a', 'b') # 將目前目錄下的a目錄複製成b目錄
```
- 來源目錄要存在,否則會產生例外並結束程式
- 可使用相對路徑或絕對路徑
- 目錄下的子目錄和檔案都會一併被複製到新目錄下
#### 檔案移動
```python=
import shutil
shutil.move('a.txt', 'D:\\Documents') # 將目前目錄下的a.txt搬移到D:\Documents目錄下
shutil.move('a.txt', 'D:\\Documents\b.txt') # 將目前目錄下的a.txt搬移到D:\Documents目錄下,並改名為b.txt
```
> **補充:**
>
> 在移動的過程中如果目的路徑有包含檔案名稱,則會一併修改檔案名稱
#### 目錄移動
`move()`函式也可以用來進行目錄的移動
```python=
import shutil
shutil.move('my-dir', 'D:\\Documents') # 將目前目錄下的my-dir子目錄移動到D:\Documents下
shutil.move('my-dir', 'D:\\Documents\my-dir-2') # 將目前目錄下的my-dir子目錄移動到D:\Documents下,並改名為my-dir-2
```
> **補充:**
>
> 如果目的地的目錄不存在,則會將原始目錄更名為該名稱,達到改變目錄名稱的效果。
#### 刪除空目錄
```python=
import shutil
shutil.rmdir('hello') # 刪除hello這個空目錄
```
> **補充:**
>
> 如果要刪除的目錄不是空的,則會刪除失敗。
#### 刪除目錄和底下的所有內容
```python=
import shutil
shutil.rmtree('hello') # 刪除hello目錄和底下所有的子目錄和檔案
```
> **注意:**
>
> 刪除後無法復原,請小心使用。
## 練習
1. 列出指定目錄下所有檔案的大小總和。
2. 將指定目錄下的文字檔檔名前面都加上編號,例如:
```
a.txt
tr.txt
obc.txt
```
變成:
```
01-a.txt
02-tr.txt
03-obc.txt
```