在zsh中,path
是一個特殊的數組變量,其內容連結到眾所周知的PATH
變量。
事實上,定義和呼叫該函數非常特別
f() { local -r path=42 }
導致錯誤f: read-only variable: path
。如果局部變數被宣告為可變的(即沒有-r
),一切都會按預期工作。我無法使用其他變數名稱重現此錯誤。
為什麼會出現此錯誤?其他名稱是否也有類似規則?
我在 macOS 10.12.6 上使用 zsh 5.2 (x86_64-apple-darwin16.0)。
答案1
TL;DR 不要重複使用“特殊內建參數”,例如path
因為它們很特殊。或根據郵件清單可以使用該-h
標誌:
% () { local -hr path=42; echo $path }
42
%
(但是更改path
為整數可能會弄亂後續程式碼,這些程式碼會忘記此覆蓋並假設path
是path
...)
接下來是更長的挖掘(但我完全錯過了-h
隱藏的東西...)
% print ${(t)path}
array-special
這是特殊變數的屬性(功能?錯誤?),但不是使用者連結的類似變數:
% () { typeset -r PATH=/blah; echo $PATH }
(anon): read-only variable: PATH
% typeset -Tar FOO=bar foo
% print $foo
bar
% print ${(t)foo}
array-readonly-tag_local
% () { local -r foo=blah; echo $foo }
blah
還有各種其他參數表現出這種行為:
% for p in $parameters[(I)*]; do print $p $parameters[$p]; done | grep array-
cdpath array-special
...
% () { local -r cdpath=42 }
(anon): read-only variable: cdpath
所以有些變數就像動物農場比其他人更特別。此錯誤訊息來自各個地方,其中Src/params.c
如果將其修改為列印哪個訊息,則我們在編譯時會發現該訊息是特定的zsh
:
% () { local -r path }
% () { local -r path=foo }
(anon): read-only variable (setarrvalue): path
是相當通用的程式碼
/**/
mod_export void
setarrvalue(Value v, char **val)
{
if (unset(EXECOPT))
return;
if (v->pm->node.flags & PM_READONLY) {
zerr("read-only variable (setarrvalue): %s", v->pm->node.nam);
freearray(val);
return;
}
這表明問題發生在其他地方;非特殊變數無疑沒有PM_READONLY
設置,但失敗的特殊變數卻設定了。下一個值得注意的地方是local
具有各種名稱的代碼( typeset
export
...)。這些都是內建的,所以可以發現潛伏在Src/builtin.c
% grep BUILTIN Src/builtin.c | grep local
BUILTIN("local", BINF_PLUSOPTS | BINF_MAGICEQUALS | BINF_PSPECIAL | BINF_ASSIGN, (HandlerFunc)bin_typeset, 0, -1, 0, "AE:%F:%HL:%R:%TUZ:%ahi:%lp:%rtux", NULL),
這些都是bin_typeset
設置了各種標誌的調用,所以讓我們研究該函數的源代碼......在評論中發誓,檢查。注意到事情很複雜,檢查一下。儘管兔子洞(當“將參數視為模式”-m
選項是不是set,這裡就是這種情況)似乎導致了這個typeset_single
功能...
有一些與POSIXBUILTINS
相關的程式碼readonly
,但在我的測試 shell 中已關閉
% print $options[POSIXBUILTINS]
off
所以我將忽略該代碼(我希望如此。這可能是修格斯的巢穴而不僅僅是兔子洞嗎?)。同時!一些調試指向透過以下行PM_READONLY
打開的標誌path
/*
* The remaining on/off flags should be harmless to use,
* because we've checked for unpleasant surprises above.
*/
pm->node.flags = (PM_TYPE(pm->node.flags) | on | PM_SPECIAL) & ~off;
這又來自於輸入on
函數時已經打開的變量typeset_single
,嘆息,所以回到bin_typeset
我們開始...好吧,基本上有一個TYPESET_OPTSTR
透過某些巨集PM_READONLY
預設啟用的變數;相反,當使用者提供的變數通過此程式碼路徑運行時,該變數PM_READONLY
將關閉並且一切正常。
對於 ZSH 開發人員來說,是否可以更改此設定以使特殊變數(例如path
可以只讀)是一個問題(嘗試 zsh-workers 郵件列表?),否則同時不要亂搞特殊變數。