戰(zhàn))
剛學(xué)shell那陣子我一度覺得cp和mv是世界上最簡單的命令。一個(gè)復(fù)制一個(gè)移動(dòng)英文單詞直譯過來就完事了。直到有一次部署項(xiàng)目我自信滿滿地敲下一行cp -r ./build /opt/app/結(jié)果起來發(fā)現(xiàn)配置文件全沒了排查了半天才意識(shí)到隱藏文件壓根沒復(fù)制過去。那之后我把cp和mv從頭到尾翻了個(gè)底朝天才發(fā)現(xiàn)這兩個(gè)命令的細(xì)節(jié)之多根本不亞于任何一門編程語言的基礎(chǔ)語法。這篇就把我的學(xué)習(xí)過程和踩坑記錄完整分享出來涵蓋cp、cp -i、cp -n、cp -r以及mv的核心用法、底層邏輯和實(shí)戰(zhàn)場景適合剛接觸shell的入門讀者也適合那些用了半年一年但偶爾還會(huì)被“覆蓋了重要文件”“復(fù)制少了文件”這類問題坑到的朋友。1. 先搞清楚cp和mv在系統(tǒng)里到底做了什么很多人學(xué)shell命令喜歡死記參數(shù)比如cp加-r是遞歸mv加-i是提示但這樣記很快會(huì)忘。我建議反過來先把這兩個(gè)命令在Linux系統(tǒng)里執(zhí)行的底層動(dòng)作搞明白參數(shù)只是這些底層動(dòng)作的開關(guān)。1.1 cp讀取源文件、創(chuàng)建目標(biāo)文件、逐字節(jié)拷貝內(nèi)容cp這個(gè)詞來自copy它做的事情可以拆成三步打開源文件讀取數(shù)據(jù)在目標(biāo)路徑創(chuàng)建新文件然后把讀到的內(nèi)容寫入這個(gè)新文件。聽起來很簡單但這里有幾個(gè)關(guān)鍵點(diǎn)容易被忽略。第一步涉及權(quán)限源文件至少要可讀目標(biāo)目錄至少要可寫否則cp直接報(bào)錯(cuò)。第二步涉及元數(shù)據(jù)新文件創(chuàng)建出來以后它的權(quán)限、時(shí)間戳、屬主和源文件并不完全一樣。默認(rèn)情況下新建文件的權(quán)限會(huì)受到當(dāng)前用戶的umask影響比如源文件是0644umask是022復(fù)制后大概率還是0644但如果umask設(shè)置不同結(jié)果就會(huì)變。時(shí)間戳更是直接變成當(dāng)前時(shí)間不是源文件的修改時(shí)間。第三步是內(nèi)容拷貝普通文件是字節(jié)流拷貝但遇到符號(hào)鏈接、設(shè)備文件、硬鏈接這些特殊類型默認(rèn)的cp并不會(huì)照搬類型而是可能解引用、可能只復(fù)制鏈接本身行為需要額外參數(shù)控制。理解這三步以后很多問題就想通了。比如為什么復(fù)制一個(gè)目錄會(huì)報(bào)錯(cuò)omitting directory因?yàn)槟J(rèn)的cp打開目錄做字節(jié)讀取是沒意義的必須加-r明確告訴它“我要遞歸處理目錄里的所有東西”。再比如為什么有時(shí)候復(fù)制出來的腳本不能執(zhí)行因?yàn)闄?quán)限位變了x位丟了需要加-p或者-a保留屬性。1.2 mv同一個(gè)文件系統(tǒng)里只是改個(gè)目錄項(xiàng)mv比cp有意思得多。它的本質(zhì)不是“把數(shù)據(jù)搬過去”而是調(diào)用rename()這個(gè)系統(tǒng)調(diào)用。在同一個(gè)文件系統(tǒng)內(nèi)rename只是修改了目錄項(xiàng)里的名字和路徑信息數(shù)據(jù)塊原地不動(dòng)所以瞬間完成就算文件有幾個(gè)GB也是一眨眼的事。這也是為什么mv比cp再加rm要快得多。但一旦跨文件系統(tǒng)比如從/home分區(qū)移動(dòng)到/data分區(qū)兩個(gè)分區(qū)是不同的文件系統(tǒng)rename就沒法直接工作了。這時(shí)候mv會(huì)退化成“復(fù)制到目標(biāo)然后刪除源文件”的兩步操作速度自然慢下來而且要保證目標(biāo)分區(qū)有足夠空間。你甚至可以認(rèn)為在跨文件系統(tǒng)時(shí)mv就是cp加rm的封裝。還有一個(gè)容易被忽略的點(diǎn)mv命令本身不區(qū)分“重命名”和“移動(dòng)”。mv oldname newname是重命名mv /path/file /another/path/是移動(dòng)但底層都是同一個(gè)rename調(diào)用。理解這一點(diǎn)后面在shell腳本里做批量重命名時(shí)思路就會(huì)清晰很多無非是把舊路徑算出來、把新路徑算出來再交給mv執(zhí)行。2. cp命令核心參數(shù)逐個(gè)拆解-i、-n、-r以及它們的組合cp的參數(shù)很多man手冊里列了幾十項(xiàng)但日常工作高頻的就那幾個(gè)。我按照標(biāo)題里點(diǎn)名的順序把-i、-n、-r拆開講透順帶補(bǔ)充幾個(gè)全是實(shí)戰(zhàn)經(jīng)驗(yàn)的關(guān)聯(lián)參數(shù)。2.1 cp -i交互確認(rèn)不是多此一舉-i是interactive的縮寫意思是“當(dāng)目標(biāo)文件已存在時(shí)逐個(gè)詢問是否覆蓋”。交互式shell里很多發(fā)行版默認(rèn)把cp做成了alias cpcp -i所以你平時(shí)敲cp a.txt b.txt如果b.txt已經(jīng)存在它會(huì)問cp: overwrite b.txt?輸入y回車才覆蓋輸入n就跳過。這個(gè)提示看著繁瑣實(shí)際是個(gè)保命設(shè)計(jì)。我見過太多人剛上手Linux復(fù)制配置文件時(shí)一個(gè)不留神就把目標(biāo)文件覆蓋了等發(fā)現(xiàn)配置被清空才拍大腿。但-i有個(gè)很關(guān)鍵的特性它是給交互式場景設(shè)計(jì)的。你在終端里敲命令它能彈出提示你在shell腳本里寫cp -i a.txt b.txt腳本不會(huì)等你輸入而是會(huì)直接失敗或者按非交互模式處理。因?yàn)槟_本的stdin通常不是終端提示信息發(fā)不出去命令就出問題。所以我的建議是交互式終端里保留alias讓默認(rèn)行為安全一點(diǎn)腳本里如果確實(shí)需要“存在就不覆蓋”的邏輯不要依賴-i直接用-n或者寫if判斷后面會(huì)講到。另外如果你習(xí)慣用\cp來繞過alias比如\cp -f a.txt b.txt這種寫法在bash里可以臨時(shí)取消別名但可讀性一般團(tuán)隊(duì)協(xié)作時(shí)不如顯式寫/usr/bin/cp來得清楚。2.2 cp -n不覆蓋模式看似簡單但坑不少-n是no-clobber的縮寫意思是“如果目標(biāo)文件已存在直接跳過不覆蓋也不詢問”。這個(gè)參數(shù)非常適合那些“只想補(bǔ)缺、不想動(dòng)已有內(nèi)容”的場景比如往一個(gè)已存在的目錄里同步素材我只想把缺失的文件補(bǔ)進(jìn)去已有的文件保持原樣。但-n有幾個(gè)坑得提前說。第一個(gè)坑是兼容性GNU coreutils的cp -n行為比較穩(wěn)定但BSD/macOS的cp實(shí)現(xiàn)對(duì)-n的處理在部分版本里有差異有些老版本甚至有bug比如第一次執(zhí)行不覆蓋第二次又覆蓋了。如果你需要在多平臺(tái)腳本里用-n建議先用cp --version或者man cp確認(rèn)一下行為。第二個(gè)坑是-n和-i同時(shí)用的問題這兩個(gè)參數(shù)邏輯上互斥在新版本GNU coreutils里直接同時(shí)給出會(huì)報(bào)錯(cuò)舊版本則可能誰在后面誰生效。寫腳本時(shí)千萬別寫cp -ni這種靠“最后一個(gè)參數(shù)生效”的戲法換臺(tái)機(jī)器就翻車。第三個(gè)坑是優(yōu)先級(jí)問題如果目標(biāo)是一個(gè)已存在的目錄cp -n file dir/不會(huì)因?yàn)槟夸洿嬖诰吞^復(fù)制它判斷的是“目錄里的具體文件是否存在”所以-n只針對(duì)最終的文件不針對(duì)中間的路徑。2.3 cp -r遞歸復(fù)制與隱藏文件陷阱-r是recursive遞歸復(fù)制整個(gè)目錄。不帶-r復(fù)制目錄會(huì)直接報(bào)錯(cuò)這個(gè)大多數(shù)人都知道。但真正讓新手栽跟頭的是“遞歸復(fù)制了目錄但隱藏文件丟了”這個(gè)經(jīng)典問題。問題往往出在這條命令上cp -r /src/* /dst/。這里的*由shell展開會(huì)匹配所有非隱藏文件和目錄但不會(huì)匹配以點(diǎn)開頭的隱藏文件。于是/src下的.env、.git、.config這些隱藏文件或目錄全部被漏掉。這個(gè)坑特別隱蔽因?yàn)閺?fù)制完以后目錄結(jié)構(gòu)看起來完整能跑起來但某些隱藏的配置缺失導(dǎo)致程序行為異常。正確做法是用cp -r /src/. /dst/注意源路徑結(jié)尾的/.這會(huì)把src目錄下所有內(nèi)容包括隱藏文件都遞歸復(fù)制過去。也可以用cp -r /src /dst/這樣會(huì)把整個(gè)src目錄變成/dst/src語義不同需要按需選擇。-r還有一個(gè)細(xì)節(jié)是符號(hào)鏈接的處理。默認(rèn)情況下cp -r遇到符號(hào)鏈接復(fù)制的是鏈接本身而不是鏈接指向的目標(biāo)內(nèi)容。這在大部分時(shí)候是合理的因?yàn)殒溄又赶虻目赡苁窍到y(tǒng)目錄你并不想把它整個(gè)復(fù)制一遍。但如果你希望解引用把鏈接指向的真實(shí)文件復(fù)制過去需要加-L反過來如果希望連鏈接都保留原樣-P或者-d更適合。還有硬鏈接cp -r默認(rèn)會(huì)為每個(gè)硬鏈接分別復(fù)制一份獨(dú)立文件如果想要保留硬鏈接關(guān)系要用--preservelinks或者干脆用cp -a。2.4 額外加分項(xiàng)-v、-p、-a、-l、-f標(biāo)題里沒列全但實(shí)際學(xué)cp繞不開這幾個(gè)參數(shù)。-v是verbose輸出詳細(xì)過程我強(qiáng)烈建議新手在關(guān)鍵操作前加一個(gè)-v能清楚看到每個(gè)文件的復(fù)制結(jié)果出問題也好定位。-p是preserve保留權(quán)限、時(shí)間戳、屬主等屬性適合備份場景。-a相當(dāng)于-dR --preserveall是“盡可能完整保留文件屬性”的遞歸復(fù)制做整個(gè)目錄的備份時(shí)最常用。-l有點(diǎn)特殊它是“創(chuàng)建硬鏈接而不是復(fù)制內(nèi)容”也就是說目標(biāo)文件和源文件共享同一份數(shù)據(jù)塊修改任一方另一方也跟著變。這個(gè)參數(shù)在做快照式目錄時(shí)很高效但不熟悉硬鏈接原理的人慎用容易改一個(gè)地方連著源頭一起改了。-f是force強(qiáng)制覆蓋但如果目標(biāo)文件只讀且當(dāng)前用戶不是屬主-f在部分情況下會(huì)嘗試先刪除再創(chuàng)建。日常使用中-f經(jīng)常和-i打架注意別在交互式終端里因?yàn)閍lias習(xí)慣把-f的效果吞了。3. mv命令的實(shí)操細(xì)節(jié)重命名、移動(dòng)與覆蓋mv的底層原理前面提過同一文件系統(tǒng)是rename跨文件系統(tǒng)是復(fù)制加刪除。這一節(jié)把它的參數(shù)、典型場景和和其他命令的配合講透。mv沒有-r這種遞歸需求因?yàn)槟夸涀鳛橐粋€(gè)整體可以被rename直接處理所以在只改路徑不改數(shù)據(jù)這個(gè)層面上mv移動(dòng)目錄比cp復(fù)制目錄要輕松得多。3.1 mv在shell腳本中的典型用法我在寫shell腳本時(shí)mv最常用的一個(gè)場景是“臨時(shí)文件替換”。比如我要把一個(gè)生成的配置文件寫入到最終位置如果直接寫目標(biāo)文件萬一程序正在讀就會(huì)讀到半截內(nèi)容。穩(wěn)妥的做法是先把內(nèi)容寫到同一個(gè)目錄下的臨時(shí)文件比如config.tmp等寫入完成、校驗(yàn)無誤后再用mv config.tmp config一次性替換。因?yàn)閙v在同一個(gè)文件系統(tǒng)內(nèi)是原子的要么舊文件還在要么新文件已經(jīng)就位不會(huì)出現(xiàn)“讀到一半”的狀態(tài)。這個(gè)模式在腳本里非常實(shí)用尤其是處理配置文件、數(shù)據(jù)文件這種需要保證完整性的場景。批量重命名是mv的另一個(gè)高頻用法。比如當(dāng)前目錄下面有一堆*.log文件我想把后綴改成.txt一個(gè)for循環(huán)就搞定了for f in *.log; do mv $f ${f%.log}.txt done這里${f%.log}是shell的參數(shù)擴(kuò)展意思是去掉變量f末尾的.log然后在后面拼上.txt。注意文件名最好用雙引號(hào)包起來尤其是文件名里有空格或者中文的時(shí)候不加引號(hào)會(huì)被shell拆成多個(gè)參數(shù)mv就把一個(gè)文件當(dāng)成兩個(gè)來操作了直接報(bào)錯(cuò)。這個(gè)坑在寫腳本時(shí)尤其常見我最初練手時(shí)就在這上面翻過車。mv配合路徑處理也很順手比如把指定日期之前的日志歸檔到舊目錄find /var/log/app -name *.log -mtime 30 -exec mv {} /var/log/app/archive/ \;find負(fù)責(zé)篩文件{}會(huì)被替換成找到的文件路徑exec mv把它們移動(dòng)到歸檔目錄。這個(gè)寫法比cp再刪要安全因?yàn)閙v不復(fù)制數(shù)據(jù)同文件系統(tǒng)下執(zhí)行速度快也不會(huì)因?yàn)榭臻g不足出問題。3.2 mv與cp -r rm在跨文件系統(tǒng)時(shí)的行為差異前文說了mv跨文件系統(tǒng)的時(shí)候會(huì)退化成“復(fù)制刪除”這就帶來幾個(gè)和cp不一樣的注意點(diǎn)。第一速度。cp -r復(fù)制多少數(shù)據(jù)就花多少時(shí)間mv跨文件系統(tǒng)同樣如此不會(huì)因?yàn)閕s一個(gè)mv就變快。所以移動(dòng)大目錄跨分區(qū)前建議先看下目標(biāo)分區(qū)的可用空間df -h一下別等到復(fù)制一半提示No space left on device才后悔。第二操作結(jié)果的一致性。mv在“復(fù)制刪除”過程中如果中途失敗它不會(huì)自動(dòng)回滾可能出現(xiàn)源文件已刪、目標(biāo)文件只復(fù)制了一部分的情況。所以關(guān)鍵數(shù)據(jù)跨文件系統(tǒng)移動(dòng)時(shí)我通常寧可保險(xiǎn)一點(diǎn)先用cp -a復(fù)制并保留屬性確認(rèn)無誤后再手動(dòng)rm -rf源目錄。cp -a可以檢查完整性比如復(fù)制完后對(duì)比文件數(shù)量、總大小甚至抽樣校驗(yàn)內(nèi)容。mv沒給你這個(gè)確認(rèn)窗口尤其適合“這個(gè)文件不重要、丟了也無所謂”的場景。第三權(quán)限和時(shí)間戳。mv在跨文件系統(tǒng)時(shí)它為了模擬rename的效果會(huì)自動(dòng)嘗試把權(quán)限、時(shí)間戳等屬性一起搬過去。但這個(gè)嘗試在遇到“當(dāng)前用戶沒有源文件屬主權(quán)限”時(shí)會(huì)靜默降級(jí)結(jié)果就是目標(biāo)文件屬性不完整。如果你在意這些別猶豫用cp -a加手動(dòng)刪除的組合步驟多一道但每個(gè)環(huán)節(jié)你都知道發(fā)生了什么。3.3 mv的交互參數(shù)-i、-n、-v與cp如出一轍mv也支持-i和-n-i在覆蓋前詢問-n不覆蓋已存在文件。行為邏輯和cp基本一致但有細(xì)微差別值得注意。由于mv在同文件系統(tǒng)里是rename如果目標(biāo)存在且被覆蓋命令執(zhí)行前不會(huì)有任何提示默認(rèn)情況下。所以在腳本里批量覆蓋文件時(shí)建議根據(jù)需求顯式加上-n或-i而不是依賴默認(rèn)行為。我見過一個(gè)真實(shí)案例一個(gè)部署腳本里寫mv new_version app.jar每次發(fā)布前都得手動(dòng)確認(rèn)舊版本的備份結(jié)果有一次沒做備份直接覆蓋了回滾只能靠重新構(gòu)建。這就是把mv想得太簡單了。另外mv的-v也很有用它會(huì)輸出renamed a.txt - b.txt這種信息。寫腳本時(shí)可以用它做日志記錄排查問題時(shí)能清楚看到每一步移動(dòng)的源和目標(biāo)。4. 實(shí)戰(zhàn)演練從單文件到批量操作的完整案例光講參數(shù)不練永遠(yuǎn)記不牢。這一節(jié)我挑三個(gè)真實(shí)場景把前面說的知識(shí)點(diǎn)串聯(lián)起來。這三個(gè)場景我都在自己的服務(wù)器上操作過可以直接照著跑。4.1 場景一完整備份一個(gè)項(xiàng)目目錄假設(shè)/data/www/demo是一個(gè)Web項(xiàng)目目錄里面有源碼、配置、上傳文件還有隱藏的.git目錄。我想在發(fā)布前整體備份一份到/backup/demo_20250101這時(shí)候直接用cp -a /data/www/demo /backup/demo_20250101這里的-a等價(jià)于-dR --preserveall它能保證遞歸復(fù)制整個(gè)目錄包括隱藏文件和隱藏目錄保留符號(hào)鏈接保留權(quán)限位、時(shí)間戳、屬主屬組備份完成后我習(xí)慣再跑一個(gè)對(duì)比命令確認(rèn)文件數(shù)一致diff -rq /data/www/demo /backup/demo_20250101如果輸出為空說明兩個(gè)目錄內(nèi)容一致如果有不同diff會(huì)打印出具體是哪個(gè)文件有差異方便快速定位。這個(gè)習(xí)慣幫我擋過好幾次備份目錄搞錯(cuò)的低級(jí)失誤。4.2 場景二按類型篩選文件再復(fù)制有時(shí)候復(fù)制不是整個(gè)目錄都要而是只要某種類型的文件。比如我要把當(dāng)前項(xiàng)目里所有.jpg圖片收集到一個(gè)/tmp/imgs目錄可以配合findfind /data/www/demo -name *.jpg -exec cp {} /tmp/imgs/ \;但這個(gè)寫法有個(gè)小坑如果兩個(gè)子目錄下有同名jpg后者會(huì)覆蓋前者。更穩(wěn)妥一點(diǎn)可以在目標(biāo)目錄里用日期分個(gè)子目錄避免沖突find /data/www/demo -name *.jpg -exec cp {} /tmp/imgs/$(date %Y%m%d)/ \;如果還想保留目錄結(jié)構(gòu)可以直接用cp --parents例如find /data/www/demo -name *.jpg -exec cp --parents {} /tmp/imgs/ \;--parents會(huì)把源文件的相對(duì)路徑也復(fù)制到目標(biāo)目錄下這樣就不會(huì)因?yàn)橥采w而丟文件。這個(gè)參數(shù)不算熱門但在整理素材、歸檔零散文件時(shí)非常好用。4.3 場景三批量重命名腳本假設(shè)有一批文件命名格式是report_20241201.csv、report_20241202.csv這樣我想把“月-日”格式改成“日-月”格式也就是把report_20241201.csv變成report_20241201.csv這種純?nèi)掌诋?dāng)然不用改但我想要的是去掉前導(dǎo)零之類的需求寫一個(gè)shell腳本for f in report_*.csv; do newnamebackup_${f} mv $f archive/$newname done這種簡單場景不適合套復(fù)雜邏輯反而容易出錯(cuò)。更常見的批量重命名場景是統(tǒng)一加前綴或者換后綴都可以用for循環(huán)加shell參數(shù)擴(kuò)展完成。需要注意的仍然是文件名的引號(hào)問題還有mv目標(biāo)目錄要提前存在否則會(huì)直接報(bào)錯(cuò)cannot move ... to ...: No such file or directory。寫腳本時(shí)我還有一個(gè)個(gè)人習(xí)慣先在前面加一個(gè)echo比如把mv $f archive/$newname臨時(shí)改成echo mv $f archive/$newname跑一遍看看輸出是否正確確認(rèn)無誤再刪掉echo真正執(zhí)行。這招在處理幾十上百個(gè)文件時(shí)能避免大量誤操作新手一定要養(yǎng)成這個(gè)習(xí)慣。4.4 場景四shell腳本里用cp -n做增量同步實(shí)際項(xiàng)目里經(jīng)常會(huì)遇到“把A目錄的新文件同步到B目錄但B目錄已有的文件不能動(dòng)”的需求。這個(gè)場景用cp -n最合適cp -rn /data/source/. /data/target/這里的-n保證已有文件不被覆蓋-r遞歸處理目錄源路徑結(jié)尾的/.確保隱藏文件也能被包含進(jìn)來。用這種方式做增量發(fā)布、素材同步、緩存預(yù)熱的腳本邏輯非常簡潔。當(dāng)然如果文件量很大并且需要?jiǎng)h除目標(biāo)目錄中多余的文件那就應(yīng)該考慮rsync比如rsync -av --ignore-existing /data/source/ /data/target/。rsync在效率和增量計(jì)算上更專業(yè)但那是另一個(gè)話題了。5. 常見問題與排查技巧實(shí)錄這一節(jié)把我自己遇到的和幫別人排查過的高頻問題整理成速查表每個(gè)問題都附上現(xiàn)象、原因和解決思路。建議收藏遇到類似現(xiàn)象直接對(duì)照。5.1 復(fù)制目錄時(shí)提示omitting directory這是cp命令最常見的報(bào)錯(cuò)之一原因很簡單源路徑是目錄但沒加-r。解決方法是加-r或者改用cp -a。如果你是在腳本里動(dòng)態(tài)拼接路徑建議先用test -d $src判斷一下是不是目錄再?zèng)Q定要不要加-r這樣腳本更健壯。5.2 隱藏文件沒復(fù)制過去前面詳細(xì)講過最常見原因是用了cp -r /src/* /dst/這種帶通配符的寫法*不匹配隱藏文件。解決辦法是改用cp -r /src/. /dst/或者用cp -r /src /dst/再注意目錄層級(jí)。排查這個(gè)問題時(shí)可以先用ls -la /dst看一眼或者用diff -rq /src /dst比對(duì)別靠肉眼猜。5.3 cp -i 在腳本里失效原因是shell腳本的執(zhí)行環(huán)境和交互式終端不同腳本里的cp只是普通外部命令不會(huì)繼承交互式shell里的alias配置。如果你希望腳本里覆蓋文件前有提示得顯式寫cp -i但更常見的是你希望腳本別卡住那就應(yīng)該避免使用-i改用-n或者-f。相反如果你在腳本里意外看到了交互提示那才是麻煩事因?yàn)闆]人輸入y腳本會(huì)一直等著甚至直接失敗。5.4 覆蓋保護(hù)為什么失效了常見原因有三個(gè)一是別名覆蓋交互式shell里默認(rèn)的alias可能是cp -i但你敲了\cp或者/usr/bin/cp直接繞過了別名二是腳本環(huán)境沒有alias三是你用了-f強(qiáng)制覆蓋-f的優(yōu)先級(jí)高于-i。理清這幾個(gè)層級(jí)覆蓋保護(hù)邏輯就很清晰了。順帶一提-n的優(yōu)先級(jí)在GNU cp里通常會(huì)阻止覆蓋但不同實(shí)現(xiàn)有差異關(guān)鍵數(shù)據(jù)別依賴這個(gè)參數(shù)兜底。5.5 權(quán)限、屬主、時(shí)間戳對(duì)不上默認(rèn)cp不保留這些屬性要保留得加-p或者-a。如果你是普通用戶復(fù)制別人的文件時(shí)即便加了-p也無法完全保留屬主只有root才能改屬主。遇到“復(fù)制以后文件時(shí)間變成當(dāng)前時(shí)間”的困惑本質(zhì)就是沒用-p。如果需要對(duì)一份資料做歸檔備份我的建議是直接用cp -a一勞永逸。5.6 mv到目標(biāo)目錄時(shí)報(bào)No such file or directory這個(gè)報(bào)錯(cuò)八成是目標(biāo)目錄本身不存在不是文件權(quán)限問題。mv不會(huì)自動(dòng)創(chuàng)建不存在的目錄。解決辦法是先mkdir -p /target/path再用mv或者直接用install -D這種替代工具。在shell腳本里通常應(yīng)該在mv之前加一個(gè)目錄判斷比如[ -d /target/path ] || mkdir -p /target/path這樣腳本跑到一半不會(huì)因?yàn)槟夸浫笔е袛唷?.7 文件復(fù)制一半中斷目標(biāo)文件是壞的無論cp還是mv跨文件系統(tǒng)復(fù)制過程中斷電或網(wǎng)絡(luò)中斷都可能導(dǎo)致目標(biāo)文件不完整。除了保證網(wǎng)絡(luò)穩(wěn)定和空間充足根本的解決思路是“先寫臨時(shí)文件再原子替換”這也是前面講過的mv臨時(shí)文件替換模式。腳本里復(fù)制重要文件時(shí)推薦先cp到隱藏的臨時(shí)文件檢查文件大小和源一致后再mv到正式位置。這樣目標(biāo)路徑在任意時(shí)刻要么是舊的完整文件要么是新的完整文件不會(huì)出現(xiàn)半截文件被其他程序讀到的問題。5.8 常用命令速查表命令核心作用典型場景常見坑cp file1 file2復(fù)制文件單文件復(fù)制目標(biāo)存在時(shí)直接覆蓋cp -i file1 file2覆蓋前詢問交互式終端操作腳本中無效cp -n file1 file2存在即跳過增量補(bǔ)充文件與-i互斥多平臺(tái)行為差異cp -r dir1 dir2遞歸復(fù)制目錄目錄整體復(fù)制通配符導(dǎo)致隱藏文件缺失cp -a src dst完整保留屬性遞歸復(fù)制備份、歸檔大目錄時(shí)速度慢cp -v file1 file2顯示復(fù)制過程排查問題—mv old new重命名/移動(dòng)文件改名、路徑調(diào)整默認(rèn)覆蓋無提示mv -i old new覆蓋前詢問交互式操作腳本中同樣無效mv -n old new存在即跳過不覆蓋的移動(dòng)同cp -n的兼容性mv -v old new顯示移動(dòng)過程日志記錄—5.9 怎么記才不會(huì)忘我覺得最好的記憶方式不是背參數(shù)表而是把cp和mv放在同一個(gè)操作場景里對(duì)比。比如“把A目錄復(fù)制成B目錄但不覆蓋已有文件”你自然會(huì)想到cp -rn A/. B/“把一個(gè)臨時(shí)文件替換成正式文件”你自然會(huì)想到mv temp config。當(dāng)你能用自己的話描述“什么場景用什么命令”的時(shí)候參數(shù)就不需要刻意背了因?yàn)樗呀?jīng)和內(nèi)化的知識(shí)綁在一起了。另外多用man cp和man mv查原始文檔中文搜索引擎里很多教程都過時(shí)了尤其是coreutils更新以后某些行為已經(jīng)變了。遇到不確定的參數(shù)直接在終端里跑一個(gè)測試目錄驗(yàn)證一下比在網(wǎng)上翻舊帖要可靠得多。個(gè)人實(shí)操體會(huì)我踩過最大的坑就是那一次隱藏文件漏復(fù)制后來養(yǎng)成了一個(gè)習(xí)慣凡是cp -r復(fù)制目錄默認(rèn)不帶*通配符凡是腳本里覆蓋文件默認(rèn)先寫到臨時(shí)文件再用mv替換凡是跨文件系統(tǒng)移動(dòng)重要數(shù)據(jù)默認(rèn)用cp -a加手動(dòng)的rm而不是一行mv了事。這三個(gè)習(xí)慣看起來謹(jǐn)慎甚至有點(diǎn)繁瑣但確實(shí)幫我擋掉了很多線上事故。最后分享一個(gè)工作流我已經(jīng)用了兩年備份目錄用cp -a增量同步用cp -rn正式文件的替換一律走“臨時(shí)文件寫入mv原子替換”。這套組合在絕大多數(shù)場景下都能cover住不需要?jiǎng)硬粍?dòng)就上rsync。等哪天這些基礎(chǔ)命令操作已經(jīng)成為肌肉記憶再去看rsync、tar管道這類高級(jí)工具你會(huì)發(fā)現(xiàn)原理都是相通的學(xué)起來特別快。