ljm 2 days ago

The author's last post was about terminal frustrations[0]

This is easily one of the most annoying ones, especially when shells will have several different locations to read config from depending on how you start the shell, or what a desktop app will do to try and pull the config in (`exec-path-from-shell` in emacs, for example).

And you can't really, say, put it in both your `.zprofile` and `.zshrc` or `.bash_profile`, `.profile`, and `.bashrc`, because then it'll get executed more than once and you'd need to maintain some kind of state to prevent that.

[0]https://jvns.ca/blog/2025/02/05/some-terminal-frustrations/

  • throw0101d 2 days ago

    > And you can't really, say, put it in both your `.zprofile` and `.zshrc` or `.bash_profile`, `.profile`, and `.bashrc`, because then it'll get executed more than once and you'd need to maintain some kind of state to prevent that.

    FWIW, profiles are loaded on 'from scratch' logins; RC files are loaded all the time.

    * https://superuser.com/questions/657848/why-do-we-have-login-...

    * https://askubuntu.com/questions/879364/differentiate-interac...

    • rascul 2 days ago

      > RC files are loaded all the time

      Not necessarily. Bash won't do it on its own for a login shell, but there's probably a bit in the /etc/profile your distro provides that does it.

  • EndShell 2 days ago

    Somewhat related there are other confusing things around how environment variables are set (especially on Linux). There are environment variables I set for QHD and UHD monitors e.g. QT_SCALE_FACTOR

    * If you are using Xorg, you put environment variables in ~/.xprofile

    * If you are using Xorg on Debian, you put the environment variables in ~/.xsessionrc

    * If you are using Wayland, you make a file in ~/.config/environment.d/ and set you configuration variables there.

    • p_ing 2 days ago

      At what point do you consider it an "environment variable" vs "application setting" (even when called an env var by the app)? Windows has it's explicit env vars and GUI or CLI methods to set them, but if I was changing something about Explorer or some other graphical portion of the OS, I'd be going to the registry, for example. Setting the display resolution or scaling factor would be another registry entry, though I'd be using a purpose-built GUI for that.

      • EndShell 2 days ago

        Well with QT_SCALE_FACTOR is goes and tells all the QT to behave in a particular way. It isn't a per application setting.

        There is a similar environment variable for Steam on Linux and I would argue that it should be an application setting but for whatever reason it isn't.

        Both of these are hacks around how Xorg (doesn't) handle fractional scaling.

        • p_ing 2 days ago

          > Well with QT_SCALE_FACTOR is goes and tells all the QT to behave in a particular way. It isn't a per application setting.

          I understand that, but your window server is just another application. On Windows I wouldn't consider scale factor being an env var, and indeed it isn't set as such -- rather that value is set in the registry.

          Maybe that's the answer -- Windows has a defacto method of setting system-wide env vars that every application inherits within a given personality but Linux/BSD does not.

          I can live with that answer :-)

    • zokier 2 days ago

      > If you are using Wayland, you make a file in ~/.config/environment.d/ and set you configuration variables there.

      This is not in any way Wayland specific, and arguably should be the default choice for setting env vars, as it should apply to all user sessions regardless of what type they are or what shell is used.

      • EndShell 2 days ago

        I didn’t know that. I have to check if it works with Xorg + Gnome on Debian because I don’t think it does.

evntdrvn 2 days ago

I've worked on maintaining internal dev tooling for some small companies for a while now, and it's a real PITA to write a robust installation script for bootstrapping a new laptop running an arbitrary shell on linux or macOS into a working environment. Way more work than it feels like it should be.

At this point I've pretty much given in and decided that a containerized dev environment is probably the better solution, but on principle it feels so unsatisfying to have to resort to this :(

(I know someone is going to mention Nix/Guix ;) but that feels like a giant rabbit hole)

t43562 2 days ago

FWIW in bash I have 2 functions:

  path_add() {
      export PATH=$PATH:$(string_join ':' $@)
  }

  path_prepend() {
      PATH=$(string_join ':' "$@"):$PATH
      export PATH
  }
These can join an arbitrary list of paths to PATH. e.g.

  path_add /usr/bin /usr/local/bin ~/bin
They depend on another one:

  string_join() {
      local join=$1; shift
      local result=$1; shift
      for p in "$@"; do
        result="${result}${join}${p}"
      done
      echo -n "$result"
      set +x
  }
I also have ones for adding and prepending to LD_LIBRARY_PATH
  • jlokier 2 days ago

    The compact one-liners below are similar but avoid adding duplicate items to PATH, so are fine to call in various init scripts.

    In Bash:

      path_append() { local p; for p; do [[ :"$PATH": =~ :"$p": ]] || PATH+=:$p; done; }
      path_prepend() { local p; for p; do [[ :"$PATH": =~ :"$p": ]] || PATH=$p:$PATH; done; }
    
    In portable POSIX shell:

      path_append() { for p; do case :"$PATH": in *:"$p":*) ;; *) export PATH="$PATH:$p" ;; esac; done; }
      path_prepend() { for p; do case :"$PATH": in *:"$p":*) ;; *) export PATH="$p:$PATH" ;; esac; done; }
  • nsb1 2 days ago

    I cribbed these from someplace - slightly different approach:

      ###################################################################
      # Add directory to path
      pathadd() {          
          newelement=${1%/}
          if [ -d "$1" ] && ! echo $PATH | grep -E -q "(^|:)$newelement($|:)" ; then
              if [ "$2" = "after" ] ; then
                  PATH="$PATH:$newelement"
              else         
                  PATH="$newelement:$PATH"
              fi
          fi
      }
     
      ###################################################################
      # Remove directory from path
      pathrm() {
          PATH="$(echo $PATH | sed -e "s;\(^\|:\)${1%/}\(:\|\$\);\1\2;g" -e \
          's;^:\|:$;;g' -e 's;::;:;g')"
      }
    • simonmic a day ago

      Do any of these guard against an empty value on either side ?

      "export PATH=$DIR:$PATH - That particular pattern is way too common, and is very dangerous if you consider the case when [$DIR or] $PATH (or whatever your variable is, like $LD_LIBRARY_PATH) isn’t set. Then, the value will be :/path/to/dir, which usually means both /path/to/dir and the current directory, which is usually both unexpected behaviour and a security concern."

    • ceph_ 2 days ago

      I'm surprised both the blog post and all the other comments don't mention how it should have logic to check if the item exists in the path before adding it. Otherwise you get duplicates added everytime you source your config.

      Your function does that so +1. Though I'd use

          [[ $PATH =~ "(^|:)$newelement($|:)" ]] 
      
      over grep -q but it functions the same.
      • rascul 2 days ago

        Another option is to set the full $PATH value explicitly instead of doing an add thing for each directory. This avoids duplicates and the extra logic, but maybe isn't as convenient.

jmholla 2 days ago

> Configure your shell to continuously save your history instead of only saving the history when the shell exits. (How to do this depends on whether you’re using bash or zsh, the history options in zsh are a bit complicated and I’m not exactly sure what the best way is)

I attempted this in bash like so:

``` PROMPT_COMMAND="history -a; $PROMPT_COMMAND" ```

But, that meant every shell was sharing the same history. Pressing up would go to the last command run anywhere, not just my current shell.

Sadly, I wasn't even trying to solve the problem Julia is talking about here. I just wanted to make sure my history was saved when I shutdown. Still haven't found a great solution to that. My attempts as using traps and signals caused weird issues.

duped 2 days ago

In my (fever) dreams, there's a directory at `/var/share/env` with the rule that regular files are <file name>=<file contents> and all other file types are ignored. The `env` program can slurp all the files to create the default environment before applying whatever customization are in dotfiles for a shell.

Want to add to path? `echo ':<directory>' >> /var/share/env/PATH` (or `env --add PATH :<directory>`, or something like that).

  • forgotpwd16 2 days ago

    Why not make your dreams real then? Here's a barebones zsh function. (Bash fails at export part. If remember right, in past, had wanted to set variables from subshell results and used some eval shenanigans.)

        function my_env() {
            ENVDIR=${ENVDIR:-/var/share/env}
            arg="$1"
            case "$arg" in
                --add)
                    echo "$3" >> $ENVDIR/"$2"
                    ;;
                *)
                    (
                        for f in $ENVDIR/*; do
                            export $(basename $f)="$(< $f | sed -z 's/\n:/:/g')"
                        done
                        [[ -z "$arg" ]] && env || env "$arg"
                    )
                    ;;
            esac
        }
    
    (Of course, that's a bad approach but your idea is certainly doable in a good way.)
  • evntdrvn 2 days ago

    you could call it a "Registry"... ;)

MisterTea 2 days ago

All this path stuff would go away if you have a proper vfs where you bind you other bins over /bin allowing $path to simply read "/bin ." and be done with it.

  • gruez 2 days ago

    How does this work in a multi-user environment? Or if you want to override PATH only in certain contexts (eg. python virtual environment, which overrides python and pip)?

    • MisterTea 2 days ago

      Per-process namespaces which gives each process its own table of mounts and binds. This is how Plan 9 works where you setup the environment with a script or via calls to mount(2) and bind(2). When you log in your profile sets up all your mounts/binds in your root namespace and every process you create thereafter inherits them. Then you change these mounts/binds as needed.

      • forgotpwd16 2 days ago

        Fwiw, this can be imitated on Linux with `unshare` or/and `nsenter`. Another brick to making a poor Plan 9 clone.

        • MisterTea 2 days ago

          It's why I prefer to use Plan 9 where I can and treat Linux as a poor mans Windows.

t43562 2 days ago

.bashrc gets run every time a shell starts

I'm not sure I'd stick a path setting in .bashrc - just because it would make it very difficult to ever override that PATH setting elsewhere.

You might want that but it might also mess up some other program's attempt to set the PATH and then e.g. run a shell command.

I usually use .bash_profile or .profile more for this sort of thing and then I have to tell my terminal program to run bash as a login shell and that gives me what I mostly expect.

  • rascul 2 days ago

    > .bashrc gets run every time a shell starts

    Probably because it's sourced from your distro provided /etc/profile. Bash will only source .bashrc on its own in some cases.

    • t43562 a day ago

      It seems to be for interactive shells. So I probably hit a problem while shelling out from my editor but potentially any IDE that has a command prompt will have trouble if it's trying to set environment variables.

mathfailure 2 days ago

I love this blog even though I usually get nothing from the articles that I didn't know before. Still read it and still love it.

Joker_vD 2 days ago

As for path duplication, I personally have

    printf '%s\n' "$PATH" | grep --color=auto -E -e '(^|:)'"$( printf '%s\n' "$1" | sed 's/[][\.|$(){}?+*^]/\\&/g' )"'($|:)' > /dev/null 2>&1
    if [ "$?" = 1 ]; then PATH="$1:$PATH" ; export PATH ; fi
in my version of pathadd().
bandrami 2 days ago

But not if the shell is running as an inferior process in an emacs buffer, in which case you have to do yet another thing.

  • b5n 2 days ago

        (use-package exec-path-from-shell
          :demand t
          :config
          (exec-path-from-shell-initialize)
          (exec-path-from-shell-copy-env "PATH"))
  • whitten 2 days ago

    Could you elaborate on the method ?

fuzzfactor 5 days ago

Here's a possible idea for an open-source project if someone is qualified.

Make it as easy as it is in Windows.

  • PaulKeeble 2 days ago

    Its consistent on windows across shells but its also a hard to find feature if you haven't been using Windows for decades and its not easily scripted. Its still kind of hard, could be a lot easier than it is.

    • duped 2 days ago

      It's in the registry, iirc. All Linux needs is a standard persistent key/value store(*) that by convention shells check for environment variables before running their rc scripts.

      So it will never happen.

      * the file system doesn't count

      • p_ing 2 days ago

        SYSTEM: HKLM\SOFTWARE\CurrentControlSet\Control\Session Manager\Environment

        User: HKCU\Environment

      • SSLy 2 days ago

        > a standard persistent key/value store()*

        systemd-kvdb

  • okkdev 2 days ago

    It's not any easier on windows tho

    • the__alchemist 2 days ago

      It is. You go to Env vars, and modify the PATH setting. It just works.

      I'm confused on the Linux point, because I thought I was the only one who had problems. Until I saw this article! Describes it well. I take responsibility; I am bad at setting env vars in Linux.

    • mock-possum 2 days ago

      I found Rapid Environment Editor for that stuff year ago, never looked back.

      • zabzonk 2 days ago

        I'd second this - REE is a great utility.

  • 01HNNWZ0MV43FF 2 days ago

    I feel like it's harder in Windows. You mean, there should be a GUI to do it instead of .bashrc?

    • p_ing 2 days ago

      Why do you feel it is harder in Windows? In Windows 11, go into the Settings app, search for 'Path', then choose "Edit environment variables <in system/in account>". You then get a graphical method to add/edit/delete entries, and the paths apply to any application you run. No need to find a dotfile to edit. No need to know how to format a particular entry, or chain it with the existing $PATH.

      On macOS it is arguably harder as you need to use Cmd-Shift-. to reveal dotfiles should you be uncomfortable editing them with vim/nano/etc. When using Omz with Terminal, a path entry could be in any number of files supporting Omz along with my zshrc. Windows doesn't have this issue.

      Windows has it's issues, but env vars are significantly easier to manipulate without much knowledge.

      • forgotpwd16 2 days ago

        Don't even have to use GUI. Can do:

          setx path "%path%;C:\own\path\"
        
        Sadly this has no error checking so it may mess things up. But it's an option.
        • p_ing 2 days ago

          Right, but that's not easier, it's quicker. You need to know about setx, the path argument, and how to properly set a new path.

    • zokier 2 days ago

      The problem is that Linux does not have solid way of setting environment variables for an user (or user session). .bashrc is relevant for only bash (obviously), but bash is not the only thing that needs PATH and other env vars.

      At least systemd brought some sanity with environment.d, but as this thread (and jvns article) shows it's not very well known. And of course there is still all sorts of weird inherited complexity like pam_env.

    • forgotpwd16 2 days ago

      The Windows has a system-wide variables (incl. PATH) applicable everywhere. In contrast, the Linux situation is touched in the submitted article.

      >All of these directions only work if you’re running the program from your shell. If you’re running the program from an IDE, from a GUI, in a cron job, or some other way, you’ll need to add the directory to your PATH in a different way, and the exact details might depend on the situation.

      >I’m honestly not sure how to handle it in an IDE/GUI because I haven’t run into that in a long time, will add directions here if someone points me in the right direction.

      The ~/.pam_environment was the equivalent to Windows' environment variables but has been deprecated and supposedly systemd's environment.d took over its functionality.

      • dingnuts 2 days ago

        I don't know what she means about setting the PATH for a GUI. If you want to run a program from the GUI you add a .desktop file to ~/.local/share/applications and provide the full path to the binary as part of the Exec key[0]

        You can also override the actual PATH variable for an application in the .desktop file as well, or any environment variable, with the Environment key, if that's what she means.

        0 https://specifications.freedesktop.org/desktop-entry-spec/la...

      • jillyboel 2 days ago

        you can just stick it in /etc/environment and reboot if you want

    • Lanolderen 2 days ago

      Tbh the moment you need to use a terminal is the moment you're googling the magic spell unless you've previously googled the magic spell.

      On Windows you just go Win->start typing "environment variable" and you get "Edit the system environment variables - Control Panel". Kinda stupid that you then need to press "Environment Variables..." but I probably wouldn't have to google it.

    • mock-possum 2 days ago

      There should certainly be a GUI to do it.

      • p_ing 2 days ago

        The difference on Linux/BSD/macOS is that the Window Server is a userland application -- you can't depend on it existing (except macOS), nor can you depend on the underlying shell being consistent on a given OS/distribution (macOS ships with a deprecated bash shell and current zsh shell).

        On Windows, Microsoft has it easy. The window server is part of the executive; Win32/conhost always exist, even if you choose to run another personality (OS/2, POSIX, et. al.).

        I would bet that there is a form of graphical editor somewhere out there on the Internet that someone developed for fun for a given shell.

      • fuzzfactor 2 days ago

        >You mean, there should be a GUI

        That must have been what Microsoft was figuring back in the 20th century.

        Even in the latest W11, it's the same basic everyday GUI as in Windows XP :)

        Just a little more intuitive now.

        In XP from the Control Panel click System > Advanced > Environment Variables.

        In Win11 from Settings click System > About > Advanced System Settings > Environment Variables.

        Pay attention to your choice of User PATH, in addition to System PATH.

        Make sure that things like %HOMEDRIVE% that you edit in, actually appear as "C:" once you are done editing, or you're not doing it ideally.

    • sieabahlpark 2 days ago

      You haven't edited env vars in the path since win 8 then. At some point they made a GUI for env vars and the path variable.

      • saint_yossarian 2 days ago

        That GUI has been there since at least XP.

        • ziml77 2 days ago

          It's improved since then though. When you edit the PATH variable it presents a list of items you can edit and reorder rather than one long delimited string

unchar1 2 days ago

> fish instructions:

> set PATH $PATH ~/.npm-global/bin

Fish has some nice utilities for these type of set calls

set --append PATH ~/.npm-global/bin