Bash – Run nvm (bash function) via sudo

bashbash-scriptfunctionsudo

I want to write a init script that should basically run

nvm use v0.11.12 && forever start /srv/index.js

as the user webconfig. nvm is a shell function that is declared in ~webconfig/.nvm/nvm.sh, which is included via source ~/.nvm/nvm.sh in webconfig's .bashrc.

I tried the following:

sudo -H -i -u webconfig nvm

echo "nvm" | sudo -H -i -u webconfig

but they fail with

-bash: nvm: command not found
-bash: line 1: nvm: command not found

When I run sudo -H -i -u webconfig and enter nvm manually in that shell, it works. What am I doing wrong?

Best Answer

The problem here, as is so often the case, is about the different types of shell:

  • When you open a terminal emulator (gnome-terminal for example), you are executing what is known as an interactive, non-login shell.

  • When you log into your machine from the command line, or run a command such as su username, or sudo -u username, you are running an interactive login shell.

So, depending on what type of shell you have started, a different set of startup files are read. From man bash:

   When  bash is invoked as an interactive login shell, or as a non-inter‐
   active shell with the --login option, it first reads and executes  com‐
   mands  from  the file /etc/profile, if that file exists.  After reading
   that file, it looks for ~/.bash_profile, ~/.bash_login, and ~/.profile,
   in  that order, and reads and executes commands from the first one that
   exists and is readable.  The --noprofile option may be  used  when  the
   shell is started to inhibit this behavior.

In other words, ~/.bashrc is ignored by login shells. Since you are using the -i option to sudo, the startup files for the user's login shell are being read (from man sudo):

 -i, --login
             Run the shell specified by the target user's password data‐
             base entry as a login shell.  This means that login-specific
             resource files such as .profile or .login will be read by the
             shell. 

So, what you can do is

  1. Define the function in the user's ~/.profile or ~/.bash_profile instead. Bear in mind that ~/.profile is ignored if ~/.bash_profile exists. Also keep in mind that ~/.bash_profile is bash-specific so I would use .profile instead, just make sure that ~/.bash_profile does not exist.

  2. Source ~/.nvm/nvm.sh from ~/.profile.