Bash

將一個目標的所有符號連結替換為指向另一個目標(在同一目錄中)的腳本

  • December 8, 2014

給定以下人為的範例:

$ mkdir -p a/b && touch a/b/original-target && touch a/b/new-target
$ mkdir -p c1/c2 && cd c1/c2 && ln -s ../../a/b/original-target && cd -
$ mkdir -p d1/d2/d3 && cd d1/d2/d3 && ln -s ../../../a/b/original-target && cd -
$ tree .
.
├── a
│   └── b
│       ├── new-target
│       └── original-target
├── c1
│   └── c2
│       └── original-target -> ../../a/b/original-target
└── d1
   └── d2
       └── d3
           └── original-target -> ../../../a/b/original-target

如何將所有符號連結替換original-target為符號連結到new-target. 您可以假設原始目標和新目標都位於同一目錄中(如上面人為設計的範例中的情況)。符號連結必須使用相對路徑。

我假設您使用的是 bash,所以我會globstar使用**. 之後,剩下的就是玩一點readlink,realpath等:

shopt -s globstar
for file in c1/** d1/**; do
   if [[ -h "$file" ]]; then
       if [[ "$(readlink -f "$file")" == "$(realpath a/b/original-target)" ]]; then
           ln -sf "$(dirname "$(readlink "$file")")"/new-target "$(dirname "$file")" && rm -f "$file"
       fi
   fi
done

pax在這些情況下真的很有用*。*事實上,如果我能找到一個pax執行POSIX 指定-o listopt=...選項的選項會更容易,但是,儘管我看起來,我還沒有找到一個這樣做的。我使用 mirabilos 維護的一個 - BSD (**?) - 據我所知,這可能是大多數其他人所做的。無論如何,您可以使用正則表達式文件名 - 和文件列表。例如:pax mirabipax

(set -e; mkdir -p a/b c1/c2 d1/d2/d3
touch a/b/original-target a/b/new-target
cd c1/c2; ln -s ../../a/b/original-target
cd ../../d1/d2/d3; ln -s ../../../a/b/original-target)

這就是你的樹。現在我將其列出:

pax -ws'|\(\..*/original-target.*\)*.*|\1|' ././ | 
pax -cvs'|\(.*\)/original|\1/new|p' \
    '././a/b/original-target' '*/original-target?*'

哪個列印…

././d1/d2/d3/original-target >> ././d1/d2/d3/new-target
lrwxrwxrwx  1 mikeserv mikeserv         0 Dec  7 18:17 ././d1/d2/d3/new-target => ../../../a/b/new-target
././c1/c2/original-target >> ././c1/c2/new-target
lrwxrwxrwx  1 mikeserv mikeserv         0 Dec  7 18:14 ././c1/c2/new-target => ../../a/b/new-target

好的,所以第一個將存檔pax寫入. 我交給它的正則表達式 arg 最常用於更改流中的文件名 - 就像我在這裡使用第二個一樣- 但通過一兩個技巧,它也可以用於過濾首先添加到存檔中的文件。ustar``stdout``-s``pax

您會看到,任何在應用 arg 後為空的文件名都-s被指定為完全從存檔中刪除。所以我使用一個正則表達式,它將匹配每個文件名,但也會從每個文件中刪除每個與保存在\1. 這是必要的,因為雖然您可以根據模式輕鬆選擇存檔中的文件,但在-writing 存檔時並非如此 - 這取決於 shell glob,因此不會遞歸。

儘管如此,所寫的正則表達式仍將/original-target跟隨任何字元,因此,在另一端-c,我在列出檔案時選擇的模式之一是*/original-target?*- 將其放在那裡。即使這樣也不是完美的——我的意思是,也許你有兩次包含該模式的連結,但是……嗯,它就是這樣——而且非常好。可以通過沿著相同的路線進行進一步的測試來處理它。無論如何,我還-c對自身進行了匹配././a/b/original-file- 因此它也從列表輸出中刪除。

在列表模式下 -pax當它不-r讀取或-w儀式時 -詳細pax列出-格式 - 包括任何軟連結目標- 但僅在應用任何適用的參數之後。無論何時以以下格式應用修飾符,都會列印這些相同的參數:ls -l``link => target``-s``-s``-v``-s|...|...|p``p

“%s>>%s\n”,原始路徑名新路徑名

所以上面我們得到的列表與././不會出現的列表分開。現在這讓事情變得非常容易,我希望你會同意。我將把它留給讀者來決定如何應用這種數據ln——它應該相當簡單——但你甚至不需要這樣做

事實上,pax規範允許您使用 cli 選項在r提前或正式時間更改存檔,以更改使用者、組、連結數據、文件名、文件源等。這些是非常強大的文件解析工具 - 但是w我最接近pax實現它們的是 GNU tar,而且只是部分實現。我還沒有找到任何影響標頭檔類型的方法——儘管你應該能夠在某種程度上做到這一點——使用 cli 選項,即使在存檔本身上使用正則表達式並不是非常困難。這些東西有一個標題欄位:

… 2 - 表示符號連結。符號連結的內容應儲存在連結名稱欄位中。

我真正想做的另一件事pax-o listopt=...。這是規範範例部分的片段:

使用選項:

-o listopt="%M %(atime)T %(size)D %(name)s"

…覆蓋標準輸出中的預設輸出描述,而是寫入:

-rw-rw--- Jan 12 15:53 2003 1492 /usr/foo/bar

使用選項:

-o listopt='%L\t%(size)D\n%.7' \
-o listopt='(name)s\n%(atime)T\n%T'

…覆蓋標準輸出中的預設輸出描述,而是寫入:

/usr/foo/bar -> /tmp   1492
/usr/fo
Jan 12 15:53 1991
Jan 31 15:53 2003

因此,如果您偶然發現pax這樣做的…好吧,您知道誰在尋找。

引用自:https://unix.stackexchange.com/questions/172008