Ecco! La funzione di shell industriale a 12 linee ... tecnicamente bash e zsh-portatile che ama devotamente il tuo script di avvio ~/.bashrc
o di tua ~/.zshrc
scelta:
# void +path.append(str dirname, ...)
#
# Append each passed existing directory to the current user's ${PATH} in a
# safe manner silently ignoring:
#
# * Relative directories (i.e., *NOT* prefixed by the directory separator).
# * Duplicate directories (i.e., already listed in the current ${PATH}).
# * Nonextant directories.
+path.append() {
# For each passed dirname...
local dirname
for dirname; do
# Strip the trailing directory separator if any from this dirname,
# reducing this dirname to the canonical form expected by the
# test for uniqueness performed below.
dirname="${dirname%/}"
# If this dirname is either relative, duplicate, or nonextant, then
# silently ignore this dirname and continue to the next. Note that the
# extancy test is the least performant test and hence deferred.
[[ "${dirname:0:1}" == '/' &&
":${PATH}:" != *":${dirname}:"* &&
-d "${dirname}" ]] || continue
# Else, this is an existing absolute unique dirname. In this case,
# append this dirname to the current ${PATH}.
PATH="${PATH}:${dirname}"
done
# Strip an erroneously leading delimiter from the current ${PATH} if any,
# a common edge case when the initial ${PATH} is the empty string.
PATH="${PATH#:}"
# Export the current ${PATH} to subprocesses. Although system-wide scripts
# already export the ${PATH} by default on most systems, "Bother free is
# the way to be."
export PATH
}
Preparati alla gloria istantanea. Quindi, piuttosto che fare questo e sperare con speranza per il meglio:
export PATH=$PATH:~/opt/bin:~/the/black/goat/of/the/woods/with/a/thousand/young
Fallo invece e assicurati di ottenere il meglio, che tu lo volessi o meno:
+path.append ~/opt/bin ~/the/black/goat/of/the/woods/with/a/thousand/young
Molto bene, definire "Il migliore".
Accodare e anteporre in modo sicuro alla corrente ${PATH}
non è la faccenda banale che viene comunemente definita. Mentre conveniente e apparentemente sensato, le linee guida del modulo export PATH=$PATH:~/opt/bin
invitano complicazioni diaboliche con:
Nomi indiretti relativi (ad es export PATH=$PATH:opt/bin
.). Mentre bash
e zsh
accettano silenziosamente e per lo più ignorano i nomi relativi nella maggior parte dei casi, i nomi relativi prefissati da uno h
o t
(e forse altri personaggi nefasti) fanno sì che entrambi si vergognino di mutilarsi nell'ala del capolavoro fondamentale di Masaki Kobayashi del 1962 Harakiri :
# Don't try this at home. You will feel great pain.
$ PATH='/usr/local/bin:/usr/bin:/bin' && export PATH=$PATH:harakiri && echo $PATH
/usr/local/bin:/usr/bin:arakiri
$ PATH='/usr/local/bin:/usr/bin:/bin' && export PATH=$PATH:tanuki/yokai && echo $PATH
binanuki/yokai # Congratulations. Your system is now face-up in the gutter.
Dirnames duplicati accidentalmente. Mentre i ${PATH}
nomi duplicati sono in gran parte innocui, sono anche indesiderati, ingombranti, leggermente inefficienti, impediscono il debuggability e promuovono l'usura dell'unità - una specie come questa risposta. Mentre gli SSD in stile NAND sono ( ovviamente ) immuni dall'usura da lettura, gli HDD non lo sono. L'accesso non necessario al filesystem su ogni tentativo di comando implica un'usura della testina di lettura non necessaria allo stesso tempo. I duplicati sono particolarmente insidiosi quando si invocano conchiglie annidate in sottoprocessi annidati, a quel punto una linea apparentemente innocua come quella export PATH=$PATH:~/wat
esplode rapidamente nel Settimo Cerchio ${PATH}
dell'Inferno come PATH=/usr/local/bin:/usr/bin:/bin:/home/leycec/wat:/home/leycec/wat:/home/leycec/wat:/home/leycec/wat
. Solo Beelzebubba può aiutarti se poi aggiungi altri dirnames su quello. (Non lasciare che questo accada ai tuoi preziosi figli. )
- Dirnames mancanti per errore. Ancora una volta, sebbene i
${PATH}
nomi mancanti siano in gran parte innocui, sono anche in genere indesiderati, ingombranti, leggermente inefficienti, impediscono il debuggability e promuovono l'usura dell'unità.
Ergo, automazione amichevole come la funzione shell definita sopra. Dobbiamo salvarci da noi stessi.
Ma ... Perché "+ path.append ()"? Perché non semplicemente append_path ()?
Per disambiguity (ad esempio, con comandi esterni negli attuali ${PATH}
funzioni di shell o di sistema definiti altrove), funzioni di shell definite dall'utente sono idealmente prefisso o come suffisso sottostringhe unici supportati da bash
e zsh
ma altrimenti vietati per BaseNames comando standard - come, ad esempio, +
.
Hey. Funziona. Non giudicarmi.
Ma ... Perché "+ path.append ()"? Perché non "+ path.prepend ()"?
Perché accodarsi alla corrente ${PATH}
è più sicuro che anteporre alla corrente ${PATH}
, a parità di condizioni, cosa che non sono mai. Sostituire i comandi a livello di sistema con comandi specifici dell'utente può essere insensato nella migliore delle ipotesi e pazzo nel peggiore dei casi. Sotto Linux, ad esempio, le applicazioni a valle generalmente si aspettano le varianti di comandi GNU coreutils piuttosto che derivate o alternative personalizzate non standard.
Detto questo, ci sono assolutamente casi d'uso validi per farlo. Definire la +path.prepend()
funzione equivalente è banale. Nebulosità sans prolix, per la sua sanità mentale condivisa:
+path.prepend() {
local dirname
for dirname in "${@}"; do
dirname="${dirname%/}"
[[ "${dirname:0:1}" == '/' &&
":${PATH}:" != *":${dirname}:"* &&
-d "${dirname}" ]] || continue
PATH="${dirname}:${PATH}"
done
PATH="${PATH%:}"
export PATH
}
Ma ... Perché non Gilles?
La risposta accettata da Gilles altrove è straordinariamente ottimale nel caso generale come "append idempotente agnostico della shell" . Nel caso comune bash
e zsh
con nessun link simbolici indesiderati, tuttavia, la pena di prestazioni necessario per rattrista il ricer Gentoo in me. Anche in presenza di collegamenti simbolici indesiderabili, è discutibile se il biforcazione di una subshell per add_to_PATH()
argomento valga il potenziale inserimento di duplicati del collegamento simbolico.
Per i casi d'uso rigorosi che richiedono l'eliminazione anche dei duplicati del zsh
collegamento simbolico, questa specifica variante lo fa tramite builtin efficienti piuttosto che fork inefficienti:
+path.append() {
local dirname
for dirname in "${@}"; do
dirname="${dirname%/}"
[[ "${dirname:0:1}" == '/' &&
":${PATH}:" != *":${dirname:A}:"* &&
-d "${dirname}" ]] || continue
PATH="${PATH}:${dirname}"
done
PATH="${PATH#:}"
export PATH
}
Nota *":${dirname:A}:"*
piuttosto che *":${dirname}:"*
l'originale. :A
è un meraviglioso zsh
-ismo tristemente assente sotto la maggior parte delle altre conchiglie - incluso bash
. Per citare man zshexpn
:
A : Trasforma un nome di file in un percorso assoluto come fa il a
modificatore, quindi passa il risultato attraverso la realpath(3)
funzione di libreria per risolvere i collegamenti simbolici. Nota: sui sistemi che non dispongono di una realpath(3)
funzione di libreria, i collegamenti simbolici non vengono risolti, quindi su tali sistemi a
e A
sono equivalenti.
Nessuna ulteriore domanda.
Prego. Goditi bombardamenti sicuri. Ora te lo meriti.