χρόνος ανάγνωσης : 13 minutes

Εκκινείτε το OpenCode στο τερματικό σας, όλο λειτουργεί. Ο πράκτορας προσπαθεί ένα`gh`— δεν βρέθηκε. Ένας`java -version`— μη παρών. Ένας`node`— πουθενά. Ωθιμε, αυτά τα εργαλεία είναι εδώ στο σου κέλυφος. Το πρόβλημα; OpenCode εκκινεί κέλυφαμη-αλληλεπιδραστικάπου δεν διαβάζουν ποτέ το σας`.zshrc`. Αυτή είναι η διαδικασία για να το diagnosticάρεις και να το διορθώσεις σωστά.

Η σκηνή: ένας τυφλός πράκτορας σε ένα κόσμο εργαλείων

Ηταν μια Τρίτη το βράδυ. Έχα μόλις εγκαταστήσειhttps://opencode.ai[OpenCode], ο πράκτορας AI που υποσχήντηκε να μεταμορφώσει τον τρόπο μου να γράφω κώδικα. Πρώτος τεστ: του ζητώ να καταγράψει τα αποθετήριά μου στο GitHub.

$ opencode
> Utilise gh pour lister mes repos
❌ bash: gh: command not found

Ξένο.`gh`λειτουργούσε τέλεια στο τερματικό μου. Δοκιμάζω κάτι άλλο:

> Vérifie la version de Java
❌ bash: java: command not found

Τότε:

> Lance le build Gradle
❌ bash: gradle: command not found

Java, Gradle, Node,gh— τίποτα δεν ήταν ορατό για τον πράκτορα. Ο τερματικός μου, όμως, βλέπτε όλα. Ως να ζούμε ο πράκτορας και εγώ σε δύο παράλληλα κόσμους.

Έχρησα ώρες για να καταλάβω. Ωρες του`echo $PATH`, de which java, της αυξανόμενης απογοήveurσης. Τέλος κατάλαβα ότι το πρόβλημα δεν ήταν τα εργαλεία — ήταν τοκέλυφος. OpenCode, όπως κάθε παράγοντας που εκκινεί υποδιαδικασίες, δουλεύει σε κέλυφημη-αλληλεπιδραστικό. Και το Zsh, σε αυτά τα shell, αγνοεί απλώς και απλά το`.zshrc`.

Το εξής είναι η πλήρης περιγραφή αυτού του διαγνωστικού, της επίλυσης, και του μαθήματος που έμαθα. Αν χρησιμοποιείτε ένα AI-agent — OpenCode, Aider, Cursor, ή ακόμα και scripts cron — αυτό το πρόβλημα θα σε ασχοληθεί κάποια στιγμή.

Ανατομή του bug: δύο shells, δύο κόσμους

Όταν ανοίγετε ένα τερματικό, το Zsh το αντιμετωπίζει ως ένα κέλυφος.διαλογικός. Φορτώνει`.zshrc`, αρχικοποιεί tudo : SDKMAN, NVM, pnpm, οι ψευδωνύμες, το ωραίο σας prompt. Το περιβάλλον σας είναι ολοκληρωμένο.

Αλλά όταν το OpenCode εκτελεί μια εντολή, δεν ξεκινάει ένα διαλογικό τερματικό. Ξεκινάει ένα κέλυφοςμη-αλληλεπιδραστικό— ένα shell εργασίας, χωρίς άνθρωπο πίσω από την οθόνη. Και το Zsh, σε αυτό το πλαίσιο, σκαί`.zshrc`. Διαβάζει μόνο`.zshenv`.

Γιατί υπάρχει αυτή η διαφορά; Επειδή ένας μη διαλογικός shell είναι σχεδιασμένος να εκτελεί scripts, όχι για να εξυπηρετεί έναν ανθρώπινο χρήστη. Η φόρτωση των aliases, του prompt και των αυτοπληρώσεων σε ένα script που τρέχει στο background θα ήταν μια απώλεια. Το πρόβλημα είναι ότι το PATH σας — η πιο κρίσιμη πληροφορία για να βρείτε τα εκτελέσιμα — είναι συχνά αρχικοποιημένο σε`.zshrc`, δεν στο`.zshenv`.

@startuml
skinparam backgroundColor #FEFEFE

actor "Εσείς" as user
actor "OpenCode\n(πράκτορας)" as agent
participant "Διαδραστικό\nShell\n(με φόρτωση .zshrc)" as ishell
participant "Μη διαδραστικό\nShell\n(αγνόηση .zshrc)" as nshell

user -> ishell : gh, java, node ✅
agent -> nshell : gh, java, node ❌

note right of ishell
Πηγή .zshrc :
- SDKMAN init
- NVM init
- ~/apps στο PATH
- pnpm στο PATH
end note

note right of nshell
Το .zshrc αγνοήθηκε!
PATH = /usr/bin:/bin
+ ορισμένοι προεπιλεγμένοι κατάλογοι
end note

@enduml

Η ρίζα του προβλήματος:Το Zsh κάνει source το αρχείο .zshrc μόνο για αλληλεπιδραστικά κέλυφη. OpenCode, όπως ο καθένας πράκτορας AI που εκκινεί υποδιαδικασίες, χρησιμοποιεί μη-αλληλεπιδραστικά κελύφη. Αυτά τα κελύφη διαβάζουν μόνο`.zshenv`.

Οι ένοχοι : .zshrc vs .zshenv

Το Zsh έχειτέσσερα αρχεία αρχικοποίησης, καθένας με έναν συγκεκριμένο ρόλο. Είναι ένα ελκυστικό σχέδιο — αλλά αυτό είναι και το κεντρό του προβλήματα:

Αρχείο προμηθευμένο όταν Ρόλος Αλλάξτε το PATH;

.zshenv

πάντα(διαλογικό + μη διαλογικό + σύνδεση)

Βασικές μεταβλητές περιβάλλοντος, PATH

✅ Ναι — είναι η θέση του SA

.zprofile

shells eisόdos uovo

Αργές εντολές (μία φορά ανά periodo)

Πιθανό

.zshrc

Μόνο αλληλεπδραστικά shells

Ψευδώνυμο, prompt, αυτόματη συμπλήρωση, διαλογικά εργαλεία

❌ Όχι για τις κ riesi μ ε σ μεταβλητές

.zlogin

Κελύφη σύνδεσης (μετά το .zshrc)

Μηνύματα καλωσορίσματος, ολοκλήρωση

σπάνια

Ο πίνακας δίνει μια ένδειξη, αλλά πρέπει να τη καταλάβουμε βαθιά. Σκεφτείτε το ως τα δωμάτια ενός σπιτού :

  • `.zshenv`είναι οαίθουσα εισόδου— Όλοι περνάνε από εδώ, επισκέπτης ή κατοίκος. Αν βάλετε κάτι εδώ, όλο το shell θα το δει.

  • `.zshrc`είναι τοσαλόνι— μόνοι οι κάτοικοι (αξυμβάτα κελύφη) μπαίνουν εκεί. Οι επισκέπτες (μη αξυμβάτα κελύφη) μένουν στην αίθουσα.

  • .zprofile et `.zlogin`είναι εξειδικευμένα τμήματα για τα login shells (όπως όταν συνδέεστε μέσω SSH).

Το δράμα είναι ότι η Majority από εμάς βάζουμε το PATH στο σαλονί. Και ο παράγοντας IA, αυτός, δεν έχει ποτέ το δικαίωμα να μπεί εκεί.

Το πρόβλημα σε ένα διάγραμμα :

@startuml
skinparam backgroundColor #FEFEFE

start

if (Shell interactif ?) then (Oui)
  :Source .zshenv;
  :Source .zprofile;
  :Source .zshrc;
  :Source .zlogin;
  note right
    SDKMAN ✅
    NVM ✅
    ~/apps ✅
    pnpm ✅
  end note
else (Non — shell non-interactif)
  :Source .zshenv uniquement;
  note right
    SDKMAN ❌
    NVM ❌
    ~/apps ❌
    pnpm ❌
  end note
endif

stop

@enduml

Όταν ανοίγετε ένα τερματικό, το Zsh είναι διαλογικό: διαβάζει`.zshrc`, όλα λειτουργούν. Όταν το OpenCode εκκινεί ένα shell για να εκτελέσει μια εντολή, το Zsh είναι μη-αλληλεπιδραστικό: το παραλείπει..zshrc, διαβάζει μόνο`.zshenv`. Et si `.zshenv`Δεν υπάρχει ή δεν περιέχει το PATH — είναι η έρημη.

Γιατί το Zsh κάνει αυτό;Αυτή είναι μια επιλογή σχεδιασμού που κληρονομήθηκε από το Unix. Ένας μη διαλογιστικός shell πρέπει να είναιγρήγορος et αναπαραγώσιμος. Η φόρτωση των ψευδώνυμων, των χρωματικών prompts και των βαρείων αρχικοποιήσεων του SDKMAN σε ένα script cron ή ένα πρόγραμμα AI θα ήταν αργό και αδύναμο. Η διαχωριστική είναι άρα λογική:`.zshenv`για το ουσιώδες (μεταβλητές, PATH),`.zshrc`για τον άνεση (aliases, prompt, complétons). Το πρόβλημα εμφανίζεται όταν βάζουμε το βασικό στον άνεση.

Βήμα βήμα διάγνωση

Πριν από τη διόρθωση, πρέπει να κατανοήσετε ακριβώς τι λείπεται. Αυτή είναι μια μέθοδος επαναληψιμαίου διαγνωστικού — κρατήστε την στα αγαπημένα αν εργάζεστε με πρόξενες ΤΝ.

1. Ελέγξτε το PATH του αjánτη

Συγκρίνουμε τι βλέπει το διαλογικό τερματικό σας με τι βλέπει ένα μη διαλογικό shell — δηλαδή τι βλέπει το OpenCode.

# Dans votre terminal interactif (tout fonctionne)
echo $PATH | tr ':' '\n' | grep -v '^/usr' | sort

Τυπικό αποτέλεσμα :

/home/cheroliv/apps
/home/cheroliv/.nvm/versions/node/v22.19.0/bin
/home/cheroliv/.sdkman/candidates/java/current/bin
/home/cheroliv/.sdkman/candidates/gradle/current/bin
/home/cheroliv/.local/share/pnpm
/home/cheroliv/.local/bin

Τώρα, ας προσομοιούμε τι βλέπει ο πράκτορας — ένα μη αλληλεπιδραστικό κέλυφος :

# Shell non-interactif : pas de .zshrc
zsh -c 'echo $PATH' | tr ':' '\n' | grep -v '^/usr' | sort

Αποτέλεσμα :

/home/cheroliv/.local/bin

Πέντε από έξι δρόμοι έχουν εξαφανιστεί.Ο πράκτορας είναι αποκομμένος 83% από το περιβάλλον του. Θα ήταν σαν να σας ζητούσαμε να μαγειρέψετε χωρίς το μισό σας τα σκεύη — θα μπορούσατε να βράσετε νερό, αλλά όχι και κάτι άλλο.

Η παραγγελία`zsh -c 'echo $PATH'`είναι το σαςδιαγνωστικό εργαλείο αριθμός 1. Αν ένα εργαλείο που χρησιμοποιείτε καθημερινά λείπει από το αποτέλεσμα, ο agentes σας AI δεν θα μπορούσε επίσης να το δει. Δοκιμάστε την πριν και μετά από κάθε αλλαγή de`.zshenv`.

2. Να εντοπίσετε τι είναι στο .zshrc αλλά όχι στο `.zshenv

Τώρα, ψάχνουμε τον ένοχο στο`.zshrc`. Φιλτράρουμε τις γραμμές που αναφέρουν τα εργαλεία μας:

grep -n 'apps\|SDKMAN\|NVM\|PNPM\|PATH' ~/.zshrc

Συνήθως βρίσκουμε :

119: PATH="/usr/bin/python3:$HOME/apps:$PATH"           # ← pas exporté !
171: export NVM_DIR="$HOME/.nvm"
172: [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"   # ← NVM init
176: nvm use --lts --silent                              # ← active une version
179: export PNPM_HOME="/home/cheroliv/.local/share/pnpm"
187: export SDKMAN_DIR="$HOME/.sdkman"
188: [[ -s "$HOME/.sdkman/bin/sdkman-init.sh" ]] && source "$HOME/.sdkman/bin/sdkman-init.sh"

Όλα είναιανόρατοςγια OpenCode. Και ο`PATH`της γραμμής 119 δεν είναι καν`export`é — δεν φεύγει ποτέ από το τρέχον shell. Είναι μια κρίσιμη τεχνική λεπτομέρεια: μια μεταβλητή χωρίς`export`Αυτό παραμένει τοπικό στον κέλυφος που το καθορίζει. Τα υπο-κέλυφα — όπως αυτά που εκκινεί το OpenCode — δεν το κληρονομούν ποτέ.

3. Ελέγξτε αν το .zshenv υπάρχει

cat ~/.zshenv 2>/dev/null || echo "FICHIER ABSENT"

Αν η απάντηση είναι « ΑΡΧΕΙΟ ΛΕΙΠΕΙ », εκεί είναι όπου όλα παίζουν.

Η λύση : .zshenv + οι σωστοί διαδρομές

Η στρατηγική είναι απλή, αλλά ζητάει ακρίβεια : να το βάλει μέσα`.zshenv` μόνοτα βασικά διαδρόμια, χωρίς να φορτώνουμε τα βαρέα scripts αρχικοποίησης. Δεν μετακινούμε`.zshrc`σε`.zshenv`— εξαγούμε το ουσιώδες.

Δημιουργήστε .zshenv με όλες τις απαραίτητες διαδρομές

`.zshenv`είναι ομόνο αρχείοότι το Zsh εγγυάται να πηγαίνει μέσαόλοιΟι contextes. Εδώ είναι όπου πρέπει να πάει ο PATH.

export SDKMAN_DIR="$HOME/.sdkman"
export NVM_DIR="$HOME/.nvm"
export PNPM_HOME="$HOME/.local/share/pnpm"
export PATH="$HOME/apps:$HOME/.sdkman/candidates/java/current/bin:$HOME/.sdkman/candidates/gradle/current/bin:$HOME/.nvm/current/bin:$PNPM_HOME:$HOME/.local/bin:$HOME/.local/share/JetBrains/Toolbox/scripts:/usr/bin/python3:$PATH"

χρ ε σ ι ο π ο ι → ή τ ε ι`$HOME/.nvm/current/bin`και όχι`$HOME/.nvm/versions/node/v22.19.0/bin`. Ο λόγος: Οι έκδοσεις του Node αλλάζουν. Ένας σκληροκωδικοποιημένος δρόμος γίνεται λάθος στον επόμενο.nvm install. Περισσότερες λεπτομέρειες στην επόμενη ενότητα.

Γιαติ όχι source sdkman-init.sh στο .zshenv ?

Η πιο ελκυστική προσέγγιση θα ήταν απλώς να το αναπαραγάγουμε σε`.zshenv`το που κάνουμε στο`.zshrc` — sourcer les scripts d’initialisation. On θα μπορούσενα είσαι πειρασμένο να κάνεις:

# ❌ MAUVAISE IDÉE
[[ -s "$HOME/.sdkman/bin/sdkman-init.sh" ]] && source "$HOME/.sdkman/bin/sdkman-init.sh"

Προβλήματα:

  • Αργός:`sdkman-init.sh`κάνει διαδικτυακές αναλογισμούς και ελέγχους σε κάθε κέλυφος. Σε ένα μη διαλογικό κέλυφος, αυτό είναι ένα περιττευτικό κόστος.

  • Ευπτορός: Η SDKMAN αναμένει έναν αλληλεπιδραστικό πλαίσιο. Η αρχικοποίηση της μπορεί να αποτύχει σιωπηλώς σε έναν αγωγό ή έναν υπο-κέλυφος.

  • αχρήσιμο: SDKMAN τοποθετεί τους υποψηφίους του σε`~/.sdkman/candidates/<tool>/current/bin`— σταθερά symlinks που δείχνουν προς την ενεργούσα έκδοση. Μπορούμε να τα χρησιμοποιήσουμεευθείως.

Η σωστή προσέγγιση: παρακάμπτουν την αρχικοποίηση του SDKMAN και δείχνουν άμεσα στους συμβολικούς σύνδεσμους`current`. Το κλειδί όλης της λύσης:χρησιμοποιήστε τη δομή των αρχείων ως σύμβαση, αντί για τον κώδικα αρχικοποίησης.

@startuml
skinparam backgroundColor #FEFEFE

rectangle "Approche LENTE\n(source sdkman-init.sh)" as slow {
  card ".zshenv source sdkman-init.sh\n2-3 secondes par shell\nVérifications réseau\nRisque d'échec" as s1 #FDEDEC
}

rectangle "Approche RAPIDE\n(symlinks directs)" as fast {
  card ".zshenv export PATH=...current/bin\n0 ms\nPas de réseau\nPas d'initialisation" as s2 #E8F8E8
}

slow --> fast : Même résultat final\nLe PATH pointe sur current/bin\ndans les deux cas

@enduml

Η περίπτωση NVM: όταν ο διαχειριστής εκδόσεων ξεχάνει να αφήσει ίχνος

Το πρόβλημα NVM

Αυτό είναι όπου η έρευνα με έφερε πιο μακριά. Ο SDKMAN έχει ένα ελεγαντο σχεδιασμό : όταν κάνουμε`sdk install java 25.0.2-tem`, δημιουργεί έναν συμβολικό σύνδεσμο :

~/.sdkman/candidates/java/current -> ~/.sdkman/candidates/java/25.0.2-tem

Αυτό το symlink είναιπάντα ενημερωμένο. δείξτε το σε`.zshenv`είστε καλυμμένοι, όσο και να είναι το shell. Beautiful.

NVM, αυτόν, δεν oluşturείτίποτατου τηλεφώνου. Δεν υπάρχει συμβολικός δεσμός`current`. Δεν υπάρχει σταθερό σημείο άγκυρας. Πρέπει να δείξουμε σε μια στιβαρή διαδρομή με έκδοση, όπως αυτό :

~/.nvm/versions/node/v22.19.0/bin/node

Στον επόμενο`nvm install 24`, αυτός ο δρόμος είναι νεκρός. δικό σας`.zshenv`θα δείχνει σε μια έκδοση που δεν είναι πια η ενεργή έκδοση. Είναι μια βόμβα με καθυστέρηση.

Γιατί το NVM κάνει αυτό;NVM λειτουργεί τροποποιώντας δυναμικά το PATH σε κάθε`nvm use`. Το είναι σχεδιασμένο για προγραμματιστές που αλλάχουν συχνά έκδοση, σε ένα αλληλεπδραστικό κέλυφος. Το συμβολικό σύνδεσμο`current`Δεν था στις αρχικές προδιαγραφές — είναι ένα έλλειμμα σχεδιασμού που θα το διόρθώσουμε εμείς.

@startuml
skinparam backgroundColor #FEFEFE

package "SDKMAN ✅" {
  [~/.sdkman/candidates/java/current] as sdk_current
  [~/.sdkman/candidates/java/25.0.2-tem/] as java_25
  [~/.sdkman/candidates/java/21.0.7-tem/] as java_21

  sdk_current --> java_25 : symlink
}

package "NVM ❌ (πριν τη διόρθωση)" as nvm_before {
  [~/.nvm/versions/node/v22.19.0/] as node_22
  [~/.nvm/versions/node/v20.16.0/] as node_20
  note right of node_22
Δεν υπάρχει symlink current!
Το `.zshenv` πρέπει να δείχνει απευθείας
Θα σπάσει στο επόμενο nvm install
  end note
}

package "NVM ✅ (μετά τη διόρθωση)" as nvm_after {
  [~/.nvm/current] as nvm_current
  [~/.nvm/versions/node/v22.19.0/] as node_22b
  [~/.nvm/versions/node/v20.16.0/] as node_20b

  nvm_current --> node_22b : symlink\n(mis à jour auto)
}

@enduml

Επειδή το NVM δεν το κάνει, το κάνουμε εμείς. Η αρχή είναι η ίδια με το SDKMAN — ένας συμβολικός σύνδεσμος.`current`που πάντα δείχνει στην ενεργό έκδοση. Το δημιουργούμε μια φορά, στη συνέχεια το αυτοματοποιούμε ώστε να ενημερώνεται αυτόματα.

# Créer le symlink initial
ln -sfn "$HOME/.nvm/versions/node/v22.19.0" "$HOME/.nvm/current"

Τώρα, στο`.zshenv`, χρησιμοποιείται :

$HOME/.nvm/current/bin

Αντί να :

# ❌ Chemin en dur — cassé au prochain changement de version
$HOME/.nvm/versions/node/v22.19.0/bin

Ένας συμβολικός σύνδεσμος δεν αξίζει τίποτα αν δεν ενημερώνεται. Ο συμβολικός σύνδεσμος`current`πρέπει να ενημερώνεται όταν κάνεις`nvm use` ou nvm install. Η λύση: έναεμπακέτο— μια συνάρτηση που περικλείει την πραγματική εντολή`nvm`και ενημερώνει το συμβολικό σύνδεσμο μετά από κάθε κλήση.

# À la fin de .zshrc, APRÈS le chargement de NVM
nvm use --lts --silent
ln -sfn "$(nvm_version_path "$(nvm current)")" "$NVM_DIR/current"

_nvm() {
  command nvm "$@"
  local rc=$?
  ln -sfn "$(nvm_version_path "$(nvm current)")" "$NVM_DIR/current"
  return $rc
}
alias nvm='_nvm'

Πώς λειτουργεί, λεπτομερώς:

@startuml
skinparam backgroundColor #FEFEFE

actor Développeur
participant "nvm wrapper\n(_nvm)" as wrapper
participant "πραγματικό nvm\n(εντολή nvm)" as realnvm
participant "~/.nvm/current\n(συμβολικός σύνδεσμος)" as symlink

Développeur -> wrapper : nvm use 20
wrapper -> realnvm : command nvm use 20
realnvm --> wrapper : version activée
wrapper -> symlink : ln -sfn .../v20.16.0 ~/.nvm/current
wrapper --> Développeur : retour

note right of symlink
Πάντα ενημερωμένο!
Χρησιμοποιείται από το .zshenv
→ προσβάσιμο από το OpenCode
end note

@enduml

ο wrapper`_nvm`καλεί την πραγματική εντολή`nvm`, και έπειτα ενημερώνει τον συμβολικό σύνδεσμο. Το ψευδώνυμο`nvm='_nvm'`Κάνε ώστε όταν πληκτρολογείς`nvm`, περνάμε μέσω του wrapper. Και στην αρχικοποίηση του shell, κάνουμε τα ίδια έπειτα`nvm use --lts`.

`nvm_version_path`είναι μια εσωτερική συνάρτηση του NVM που λύνει το πλήρες μονοπάτι μιας έκδοσης. Αποτρέπει την ανάγκη να αναπαιράτε το μονοπάτι με τον χέρι.

Πίνακας συνοπτικός των διαδρομών

Πριν μεταβείτε στα παγίδια, μια οπτική σύνοψη της μετασχηματισμού. Στο αριστερό μέρος, αυτό που είχατε (όλων στο`.zshrc`, αόρατο για τον πράκτορα). στα δεξιά, το που έχετε τώρα (οι βασικοί δρόμοι στο`.zshenv`, ορατές παντού).

Εργαλείο Πριν (.zshrc μόνο) Μετά (.zshenv + symlink) Ορατό από το OpenCode ?

~/apps(gh, vscode)

PATH δεν εξάγεται στο .zshrc

`$HOME/apps`sto .zshenv

✅

SDKMAN Java

`sdkman-init.sh`φορτωμένο από .zshrc

`$HOME/.sdkman/candidates/java/current/bin`σε το .zshenv

✅

SDKMAN Gradle

`sdkman-init.sh`φορτωμένο στο .zshrc

`$HOME/.sdkman/candidates/gradle/current/bin`σε .zshenv

✅

NVM Node

`nvm use --lts`σε το .zshrc

`$HOME/.nvm/current/bin`σε .zshenv (σύνδεσμος)

✅

pnpm

`$PNPM_HOME`σε .zshrc

`$PNPM_HOME`σε .zshenv

✅

JetBrains Toolbox

Προστέθηκε αυτόματα από το Toolbox στο .zshrc

`$HOME/.local/share/JetBrains/Toolbox/scripts`σε .zshenv

✅

Python 3

`/usr/bin/python3`στο PATH .zshrc

Εκφραστικά στο .zshenv

✅

Παγίδες και μέτρα μείωσης

Δεν είναι τέλεια αυτή η προσέγγιση. Αυτά είναι τα προβλήματα που συνάντησα, και πώς να τα αποφύγω.

Παγίδα Περιγραφή μείωση

PATH διπλωμένο

Αν το .zshenv και το .zshrc προσθέσουν την ίδια διαδρομή, εμφανίζεται δύο φορές.

.zshenv`είναι προμητευμένοπριν.zshrc. Τα δύο διαβάζονται σε έναν αλληλεπιδραστικό κέλυφος. Το PATH μπορεί να διπλασιαστεί. Είναι κοσμητικό, όχι λειτουργικό. Για να το αποφύγετε: μην βάλετε στο .zshenv μόνο τις διαδρομές λειπόντα του προεπιλεγμένου PATH.

NVM : σκληρά κωδικοποιημένη έκδοση στο .zshenv

να βάλω`~/.nvm/versions/node/v22.19.0/bin`Το σκληρόκωδικό γίνεται ψευδές στον επόmeno`nvm install`

Χρησιμοποίησε το symlink`$HOME/.nvm/current/bin`+ το wrapper`_nvm`στο .zshrc

SDKMAN : πηγα sdkman-init.sh στο .zshenv

Αργός, ευαίσθητος, άχρηστος σε ένα μη διαλογικό κέλυφος

Χρησιμοποιείτε τα symlinks`candidates/<tool>/current/bin`ευθεία

Λείπων ψευδώνυμο μετά την τροποποίηση

Το περιβλήμα`nvm='_nvm'`στο .zshrc δεν παίρνει εφαρμογή μέχρι να φορτωθεί ξανά

Επανεκκίνηση του κελυφού ή`source ~/.zshrc`

Το OpenCode δεν βλέπει τις αλλαγές

Ο πράκτορας έχει ήδη εκκινήσει τα υπο-κέλυθρα του με το παλαιό .zshenv

Επανεκκίνησε το OpenCode μετά την τροποποίηση του .zshenv

.zshenv υπερφορτωμένο

Βάλτε βαρείς λειτουργίες ή αλληλεπδραστικές αρχικοποιήσεις στο .zshenv

.zshenv = μεταβλητές περιβάλλοντος + μόνο PATH. Δεν`source`, χωρίς βαρείς λειτουργίες, χωρίς προτροπές.

Μάθηματα που μάθαμε

  1. .zshrc` είναι διαλογικό, .zshenv είναι καθολικό— Αν μια μεταβλητή πρέπει να υπάρχει σε όλους τους κελύφη (πρόξενες AI, cron, σενάρια, IDE), αυτή πάει σε`.zshenv`. Το σαλόνι είναι άνετο, αλλά το έσοδος είναι το μοναδικό μέρος όπου όλοι περνούν.

  2. Οι διαχειριστές εκδόσεων δεν είναι ίσοι— SDKMAN δημιουργεί symlinks`current`κατὰ σχέδιο. NVM όχι. Πρέπει να γεμίσουμε αυτό το έλλειμμα χειροκίνητα. Αυτό είναι ένα σημαντικό μάθημα: πριν ρυθμίσετε τη μεταβίβαση PATH σας, ελέγξτε αν ο διαχειριστής σας προσφέρει ένα σταθερό σημείο αγκύρωσης.

  3. Μην φορτώσετε τα σενάρια αρχικοποίησης στο .zshenv—sdkman-init.sh et `nvm.sh`είναι σχεδιασμένοι για έναν αλληλεπιδραστικό shell. Είναι αργοί και απρόσμενες σε ένα μη-αλληλεπιδραστικό περιβάλλον. Τα symlinks`current`αρκούν και είναι άμεσα.

  4. Πάντα να δοκιμάζετε σε μη-διαλογικό shell—`zsh -c 'echo $PATH'`Προσομοιώνει ακριβώς τι βλέπει ο πράκτορας. Αυτό είναι ο έλεγχος επικύρωσης. Χωρίς αυτόν τον έλεγχο, δεν ξέρετε αν η ρύθμιση σας λειτουργεί για τους πράκτορες.

  5. Το μοτίβο wrapper είναι επαναχρησιμοποιήσιμο— Το ίδιο μοτίβο`_nvm`+`alias nvm='_nvm'`Εφαρμόζεται σε κάθε εργαλείο που τροποποιεί το PATH δυναμικά, χωρίς να αφήνει μια σταθερή ίχνη. Είναι ένα εργαλείο περισσότερο στο κουτί σου των ιδεών.

@startuml
skinparam backgroundColor #FEFEFE

rectangle "ΠΡΙΝ" as avant {
  card "Διαδραστικό shell : ✅\nΜη διαδραστικό shell : ❌\nOpenCode : ❌\nCron : ❌\nScripts : ❌" as av1 #FDEDEC
}

rectangle "ΜΕΤΆ" as apres {
  card "Διαδραστικό shell : ✅\nΜη διαδραστικό shell : ✅\nOpenCode : ✅\nCron : ✅\nScripts : ✅" as ap1 #E8F8E8
}

avant --> apres : .zshenv +\nsymlink ~/.nvm/current

@enduml

τελική επαλήθευση

Μετά που το δημιούργησε`.zshenv`και το symlink NVM, επαληθεύστε ότι όλα λειτουργούν — και στους δύο περιπτώσεις :

# Shell interactif (votre terminal)
gh --version && java -version && node --version
# Shell non-interactif (simulation OpenCode)
zsh -c 'gh --version && java -version && node --version'

Οι δύο πρέπει να επιτυχουν. Αν ναι, οπρόσωπος AI σας θα βλέπει τα ίδια εργαλεία που βλέπετε εσείς. Αν όχι, επιστρέψτε στο βήμα-βήμα διαγνωστικό — πιθανότατα ξεχάσατε ένα δρόμο ή ο symlink NVM δεν είναι ενημερωμένος.

Αυτοματοποιήστε αυτό το τεστ.Προσθέστε αυτόν τον έλεγχο σε ένα script healthcheck που τρέχετε μετά από κάθε ενημέρωση των εργαλείων σας. Ένας`zsh -c 'which java && which node && which gh'`Στην CI, είναι μια ασφάλιση εναντίον των εκπλήψεων.

_ Ένας μη αλληλεπιδραστικός shell είναι σαν έναν σιωπηλό κλήρο: διαβάζει μόνο αυτό που εμφανίζεται στην πόρτα της εισόδου. Αν το PATH βρίσκεται στο σαλόνι, το δεν θα το βλέπει ποτέ. _

Σύνδεσμοι

Επίσημη τεκμηρίωση

Εργαλεία που αναφέρθηκαν

Για να πάτε περαιτέρω

Σχετικά άρθρα