在 Linux 中如何移動檔案

2019-09-24 16:29:00

無論你是剛接觸 Linux 的檔案移動的新手還是已有豐富的經驗,你都可以通過此深入的文章中學到一些東西。

在 Linux 中移動檔案看似比較簡單,但是可用的選項卻比大多數人想象的要多。本文介紹了初學者如何在 GUI 和命令列中移動檔案,還介紹了底層實際上發生了什麼,並介紹了許多有一定經驗的使用者也很少使用的命令列選項。

移動什麼?

在研究移動檔案之前,有必要仔細研究移動檔案系統物件時實際發生的情況。當檔案建立後,會將其分配給一個索引節點inode,這是檔案系統中用於資料儲存的固定點。你可以使用 ls 命令看到檔案對應的索引節點:

$ ls --inode example.txt7344977 example.txt

移動檔案時,實際上並沒有將資料從一個索引節點移動到另一個索引節點,只是給檔案物件分配了新的名稱或檔案路徑而已。實際上,檔案在移動時會保留其許可權,因為移動檔案不會更改或重新建立檔案。(LCTT 譯註:在不跨卷、分割區和記憶體時,移動檔案是不會重新建立檔案的;反之亦然)

檔案和目錄的索引節點並沒有暗示這種繼承關係,而是由檔案系統本身決定的。索引節點的分配是基於檔案建立時的順序分配的,並且完全獨立於你組織計算機檔案的方式。一個目錄“內”的檔案的索引節點號可能比其父目錄的索引節點號更低或更高。例如:

$ mkdir foo$ mv example.txt foo$ ls --inode7476865 foo$ ls --inode foo7344977 example.txt

但是,將檔案從一個硬碟機移動到另一個硬碟機時,索引節點基本上會更改。發生這種情況是因為必須將新資料寫入新檔案系統。因此,在 Linux 中,移動和重新命名檔案的操作實際上是相同的操作。無論你將檔案移動到另一個目錄還是在同一目錄使用新名稱,這兩個操作均由同一個底層程式執行。

本文重點介紹將檔案從一個目錄移動到另一個目錄。

用滑鼠移動檔案

圖形化使用者介面是大多數人都熟悉的友好的抽象層,位於複雜的二進位制資料集合之上。這也是在 Linux 桌面上移動檔案的首選方法,也是最直觀的方法。從一般意義上來說,如果你習慣使用桌上型電腦,那麼你可能已經知道如何在硬碟機上移動檔案。例如,在 GNOME 桌面上,將檔案從一個視窗拖放到另一個視窗時的預設操作是移動檔案而不是複製檔案,因此這可能是該桌面上最直觀的操作之一:

Moving a file in GNOME.

而 KDE Plasma 桌面中的 Dolphin 檔案管理器預設情況下會提示使用者以執行不同的操作。拖動檔案時按住 Shift 鍵可強制執行移動操作:

Moving a file in KDE.

在命令列移動檔案

用於在 Linux、BSD、Illumos、Solaris 和 MacOS 上移動檔案的 shell 命令是 mv。不言自明,簡單的命令 mv <source> <destination> 會將原始檔移動到指定的目標,源和目標都由絕對相對檔案路徑定義。如前所述,mvPOSIX 使用者的常用命令,其有很多不為人知的附加選項,因此,無論你是新手還是有經驗的人,本文都會為你帶來一些有用的選項。

但是,不是所有 mv 命令都是由同一個人編寫的,因此取決於你的作業系統,你可能擁有 GNU mv、BSD mv 或 Sun mv。命令的選項因其實現而異(BSD mv 根本沒有長選項),因此請參閱你的 mv 手冊頁以檢視支援的內容,或安裝你的首選版本(這是開源的奢侈之處)。

移動檔案

要使用 mv 將檔案從一個資料夾移動到另一個資料夾,請記住語法 mv <source> <destination>。 例如,要將檔案 example.txt 移到你的 Documents 目錄中:

$ touch example.txt$ mv example.txt ~/Documents$ ls ~/Documentsexample.txt

就像你通過將檔案拖放到資料夾圖示上來移動檔案一樣,此命令不會將 Documents 替換為 example.txt。相反,mv 會檢測到 Documents 是一個資料夾,並將 example.txt 檔案放入其中。

你還可以方便地在移動檔案時重新命名該檔案:

$ touch example.txt$ mv example.txt ~/Documents/foo.txt$ ls ~/Documentsfoo.txt

這很重要,這使你不用將檔案移動到另一個位置,也可以重新命名檔案,例如:

$ touch example.txt $ mv example.txt foo2.txt $ ls foo2.txt`

移動目錄

不像 cp 命令,mv 命令處理檔案和目錄沒有什麼不同,你可以用同樣的格式移動目錄或檔案:

$ touch file.txt$ mkdir foo_directory$ mv file.txt foo_directory$ mv foo_directory ~/Documents

安全地移動檔案

如果你移動一個檔案到一個已有同名檔案的地方,預設情況下,mv 會用你移動的檔案替換目標檔案。這種行為被稱為清除clobbering,有時候這就是你想要的結果,而有時則不是。

一些發行版將 mv 別名定義為 mv --interactive(你也可以自己寫一個),這會提醒你確認是否覆蓋。而另外一些發行版沒有這樣做,那麼你可以使用 --interactive-i 選項來確保當兩個檔案有一樣的名字而發生衝突時讓 mv 請你來確認。

$ mv --interactive example.txt ~/Documentsmv: overwrite '~/Documents/example.txt'?

如果你不想手動干預,那麼可以使用 --no-clobber-n。該選項會在發生衝突時靜默拒絕移動操作。在這個例子當中,一個名為 example.txt 的檔案以及存在於 ~/Documents,所以它不會如命令要求從當前目錄移走。

$ mv --no-clobber example.txt ~/Documents$ lsexample.txt

帶備份的移動

如果你使用 GNU mv,有一個備份選項提供了另外一種安全移動的方式。要為任何衝突的目標檔案建立備份檔案,可以使用 -b 選項。

$ mv -b example.txt ~/Documents$ ls ~/Documentsexample.txt    example.txt~

這個選項可以確保 mv 完成移動操作,但是也會保護目錄位置的已有檔案。

另外的 GNU 備份選項是 --backup,它帶有一個定義了備份檔案如何命名的引數。

  • existing:如果在目標位置已經存在了編號備份檔案,那麼會建立編號備份。否則,會使用 simple 方式。
  • none:即使設定了 --backup,也不會建立備份。當 mv 被別名定義為帶有備份選項時,這個選項可以覆蓋這種行為。
  • numbered:給目標檔名附加一個編號。
  • simple:給目標檔案附加一個 ~,當你日常使用帶有 --ignore-backups 選項的 ls 時,這些檔案可以很方便地隱藏起來。

簡單來說:

$ mv --backup=numbered example.txt ~/Documents$ ls ~/Documents-rw-rw-r--. 1 seth users 128 Aug  1 17:23 example.txt-rw-rw-r--. 1 seth users 128 Aug  1 17:20 example.txt.~1~

可以使用環境變數 VERSION_CONTROL 設定預設的備份方案。你可以在 ~/.bashrc 檔案中設定該環境變數,也可以在命令前動態設定:

$ VERSION_CONTROL=numbered mv --backup example.txt ~/Documents$ ls ~/Documents-rw-rw-r--. 1 seth users 128 Aug  1 17:23 example.txt-rw-rw-r--. 1 seth users 128 Aug  1 17:20 example.txt.~1~-rw-rw-r--. 1 seth users 128 Aug  1 17:22 example.txt.~2~

--backup 選項仍然遵循 --interactive-i 選項,因此即使它在執行備份之前建立了備份,它仍會提示你覆蓋目標檔案:

$ mv --backup=numbered example.txt ~/Documentsmv: overwrite '~/Documents/example.txt'? y$ ls ~/Documents-rw-rw-r--. 1 seth users 128 Aug  1 17:24 example.txt-rw-rw-r--. 1 seth users 128 Aug  1 17:20 example.txt.~1~-rw-rw-r--. 1 seth users 128 Aug  1 17:22 example.txt.~2~-rw-rw-r--. 1 seth users 128 Aug  1 17:23 example.txt.~3~

你可以使用 --force-f 選項覆蓋 -i

$ mv --backup=numbered --force example.txt ~/Documents$ ls ~/Documents-rw-rw-r--. 1 seth users 128 Aug  1 17:26 example.txt-rw-rw-r--. 1 seth users 128 Aug  1 17:20 example.txt.~1~-rw-rw-r--. 1 seth users 128 Aug  1 17:22 example.txt.~2~-rw-rw-r--. 1 seth users 128 Aug  1 17:24 example.txt.~3~-rw-rw-r--. 1 seth users 128 Aug  1 17:25 example.txt.~4~

--backup 選項在 BSD mv 中不可用。

一次性移動多個檔案

移動多個檔案時,mv 會將最終目錄視為目標:

$ mv foo bar baz ~/Documents$ ls ~/Documentsfoo   bar   baz

如果最後一個專案不是目錄,則 mv 返回錯誤:

$ mv foo bar bazmv: target 'baz' is not a directory

GNU mv 的語法相當靈活。如果無法把目標目錄作為提供給 mv 命令的最終引數,請使用 --target-directory-t 選項:

$ mv --target-directory=~/Documents foo bar baz$ ls ~/Documentsfoo   bar   baz

當從某些其他命令的輸出構造 mv 命令時(例如 find 命令、xargsGNU Parallel),這特別有用。

基於修改時間移動

使用 GNU mv,你可以根據要移動的檔案是否比要替換的目標檔案新來定義移動動作。該方式可以通過 --update-u 選項使用,在BSD mv 中不可用:

$ ls -l ~/Documents-rw-rw-r--. 1 seth users 128 Aug  1 17:32 example.txt$ ls -l-rw-rw-r--. 1 seth users 128 Aug  1 17:42 example.txt$ mv --update example.txt ~/Documents$ ls -l ~/Documents-rw-rw-r--. 1 seth users 128 Aug  1 17:42 example.txt$ ls -l

此結果僅基於檔案的修改時間,而不是兩個檔案的差異,因此請謹慎使用。只需使用 touch 命令即可愚弄 mv

$ cat example.txtone$ cat ~/Documents/example.txtonetwo$ touch example.txt$ mv --update example.txt ~/Documents$ cat ~/Documents/example.txtone

顯然,這不是最智慧的更新功能,但是它提供了防止覆蓋最新資料的基本保護。

移動

除了 mv 命令以外,還有更多的移動資料的方法,但是作為這項任務的預設程式,mv 是一個很好的通用選擇。現在你知道了有哪些可以使用的選項,可以比以前更智慧地使用 mv 了。