How to update the /etc/profile within the sudo bash without rebooting or logging out - linux

Currently right now I am creating a script that updates the path and environment variable of the profile within my raspberry-pi
I have created a script within the /etc/profile.d/sdk.sh to create a environment variable. Now it does not updates within my env, How can I add/update my environment variable without rebooting or logging-out of the system.
My script:
SDK_SH_FILE="/etc/profile.d/sdk.sh"
EXPORT_SDK_HOME="export SDK_HOME=/edit/"
echo -e "$EXPORT_SDK_HOME" > "$SDK_SH_FILE"
It is run using: cat my-script | sudo bash
Currently it is not updating my env unless I logout or reboot the system.

After editing sdk.sh, you need to load it in the current shell with:
source /etc/profile.d/sdk.sh

You have two choices for this job:
source /etc/profile.d/sdk.sh
OR
. /etc/profile.d/sdk.sh

I have just tried out Barmar's suggestion. It works to update the current bash session. If you close the terminal window and open a new one, you need to run source again.
Also, the new values are only concatenated to the environment variable rather than replacing the old values. So, it is still better to log out and log in again.

You can update the current shell environment by sourcing a script, because that way it runs in the same shell instance, but you need to acquire privileges to update sdk.sh (so shell redirections won't work), right?
The solution is to separate the write operation that requires privileges (calling only that via sudo).
Here the UNIX toolbox comes to the rescue with tee, a program that takes files as parameters, reads from it's standard input, and copies to it's standard output and to the files specified as parameters; this being a separate program, and opening it's parameters on it's own, can be called with sudo just fine.
Solution
export SDK_HOME=/edit/
typeset -p SDK_HOME | sudo tee /etc/profile.d/sdk.sh >/dev/null
Now, you need to source this file, instead of calling it, like so:
. ./my-script.
Here I used typeset -p to avoid repeating myself, it reproduces the declarations of the specified variables.
Variables and startup files
Variables are handed down from process to process in the environment, which is a portion of memory every process gets from it's parent process.
Shells behave differently depending on how they're called; a login shell will load a user/system profile file, i.e. /etc/profile (or a similar file at the home directory), and the rest of the session will get variables from it through the environment (thus updates to the file don't matter); normally all other interactive instances of the shell load a secondary file, in the case of BASH it's $HOME/.bashrc or /etc/bashrc (which login shells don't load).

Related

Getting bash script to update parent shell's Environment

I am attempting to write a bash command line tool that is usable immediately after installation, i.e. in the same shell as its installation script was called. Lets say install-script.sh (designed for Ubuntu) looks like:
# Get the script's absolute path:
pushd `dirname $0` > /dev/null
SCRIPTPATH=`pwd`
popd > /dev/null
# Add lines to bash.bashrc to export the environment variable:
echo "SCRIPT_HOME=${SCRIPTPATH}" >> /etc/bash.bashrc
echo "export SCRIPT_HOME" >> /etc/bash.bashrc
# Create a new command:
cp ${SCRIPTPATH}/newcomm /usr/bin
chmod a+x /usr/bin/newcomm
The idea is that the new command newcomm uses the SCRIPT_HOME environment variable to reference the main script - which is also in SCRIPTPATH:
exec "${SCRIPT_HOME}/main-script.sh"
Now, the updated bash.bashrc hasn't been loaded into the parent shell yet. Worse, I cannot source it from within the script - which is running in a child shell. Using export to change SCRIPT_HOME in the parent shell would at best be duct-taping the issue, but even this is impossible. Also note that the installation script needs to be run using sudo so it cannot be called from the parent shell using source.
It should be possible since package managers like apt do it. Is there a robust way to patch up my approach? How is this usually done, and is there a good guide to writing bash installers?
You can't. Neither can apt.
A package manager will instead just write required data/variables to a file, which are read either by the program itself, by a patch to the program, or by a wrapper.
Good examples can be found in /etc/default/*. These are files with variable definitions, and some even helpfully describe where they're sourced from:
$ cat /etc/default/ssh
# Default settings for openssh-server. This file is sourced by /bin/sh from
# /etc/init.d/ssh.
# Options to pass to sshd
SSHD_OPTS=
You'll notice that none of the options are set in your current shell after installing a package, since programs get them straight from the files in one way or another.
The only way to modify the current shell is to source a script. That's unavoidable, so start there. Write a script that is sourced. That script will in turn call your current script.
Your current script will need to communicate with the sourced one to tell it what to change. A common way is to echo variable assignments that can be directly executed by the caller. For instance:
printf 'export SCRIPT_HOME=%q\n' "$SCRIPTPATH"
Using printf with %q ensures any special characters will be escaped properly.
Then have the sourced script eval the inner script.
eval "$(sudo install-script.sh)"
If you want to hide the sourceing of the top script you could hide it behind an alias or shell function.

Import PATH environment variable into Bash script launched with cron

When creating Bash scripts, I have always had a line right at the start defining the PATH environment variable. I recently discovered that this doesn't make the script very portable as the PATH variable is different for different versions of Linux (in my case, I moved the script from Arch Linux to Ubuntu and received errors as various executables weren't in the same places).
Is it possible to copy the PATH environment variable defined by the login shell into the current Bash script?
EDIT:
I see that my question has caused some confusion resulting in some thinking that I want to change the PATH environment variable of the login shell with a bash script, which is the exact opposite of what I want.
This is what I currently have at the top of one of my Bash scripts:
#!/bin/bash
PATH=/usr/local/sbin:/usr/local/bin:/usr/bin:/usr/bin/site_perl:/usr/bin/vendor_perl:/usr/bin/core_perl
# Test if an internet connection is present
wget -O /dev/null google.com
I want to replace that second line with something that copies the value of PATH from the login shell into the script environment:
#!/bin/bash
PATH=$(command that copies value of PATH from login shell)
# Test if an internet connection is present
wget -O /dev/null google.com
EDIT 2: Sorry for the big omission on my part. I forgot to mention that the scripts in question are being run on a schedule through cron. Cron creates it's own environment for running the scripts which does not use the environment variables of the login shell or modify them. I just tried running the following script in cron:
#!/bin/bash
echo $PATH >> /home/user/output.txt
The result is as follows. As you can see, the PATH variable used by cron is different to the login shell:
user#ubuntu_router:~$ cat output.txt
/usr/bin:/bin
user#ubuntu_router:~$ echo $PATH
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games
Don't touch the user's PATH at all unless you have a specific reason. Not doing anything will (basically) accomplish what you ask.
You don't have to do anything to get the user's normal PATH since every process inherits the PATH and all other environment variables automatically.
If you need to add something nonstandard to the PATH, the usual approach is to prepend (or append) the new directory to the user's existing PATH, like so:
PATH=/opt/your/random/dir:$PATH
The environment of cron jobs is pretty close to the system's "default" (for some definition of "default") though interactive shells may generally run with a less constrained environment. But again, the fix for that is to add any missing directories to the current value at the beginning of the script. Adding directories which don't exist on this particular system is harmless, as is introducing duplicate directories.
I've managed to find the answer to my question:
PATH=$PATH:$(sed -n '/PATH=/s/^.*=// ; s/\"//gp' '/etc/environment')
This command will grab the value assigned to PATH by Linux from the environment file and append it to the PATH used by Cron.
I used the following resources to help find the answer:
How to grep for contents after pattern?
https://help.ubuntu.com/community/EnvironmentVariables#System-wide_environment_variables

Linux basics - automating a script execution

When beginning to work, I have to run several commands:
source work/tools
cd work/tool
source tool
setup_tool
Off course, doing this a few times a day is really annonying, so I tried to make a bash script tool where I put these commands and put it in /user/bin to run it with command
tool
However, there is a problem. When i run the script and then try to work by typing some of the tool-based commands, it does not work.
I figured out, that it is fine, since if I make a script and then run it, the script seems to run in the same terminal window, but what it really does is, that it behaves as if it created a "hidden window" for its execution and after termination of the script, the "hidden window" terminates too. So I am asking - is there a way to automatize the source command?
I have tried using xterm -hold -e command, but it runs the programmed script in the new window. Obviously, I don't want that. How can I achieve running it in the current window?
Don't put files like that in /usr/bin. As a general rule you don't want to mess with the distribution owned locations like that. You can use /usr/local/bin if you need a system-wide location or you can create a directory in your home directory to hold things like this that are for your own usage (and add that to the $PATH).
What you've noticed is that when run as a script on its own (tool, /path/to/tool, etc.) that the script runs in its own shell session (nothing to do with terminal windows as-such) and you don't want that (as the changes the script makes don't persist to your current shell session).
What you want to do instead is "source"/run the script in your current session. Which you are already doing with that set of commands you listed (source work/tools is doing exactly that).
So instead of running tool or /path/to/tool instead use source /path/to/tool or . /path/to/tool.
As fedorqui correctly points out you don't even need a script for this anywhere as you can just make a shell function for this instead (in your normal shell startup files .bashrc, etc.) and then just run that function when you need to so that setup.
Be careful to use full paths for things when you do this though since you, presumably, want this to work no matter what directory you happen to be in when you run it.
It doesn't create a new hidden window, nor does it create a terminal. What happens is that if you're running a script, normally it runs on a new shell process. The script you're running is supposed to modify the shell environment, but if you're running the script in a new shell process, that shell process's environment is the one that gets modified, instead of your shell environment.
Scripts that needs to modify the current shell environments usually must be run with the source command. What you need to do is to run the script in the current shell. So you should do source /path/to/tool.
If you want to be able to source the script with just tool, put this in your alias file/shell startup (check your distro doc where the file is, but it's usually either .bash_aliases or .bashrc):
alias tool="source /path/to/tool"

Crontab Source File

Recently I created a bash script which I am supposed to run in cron.
After preparing the bash script and its normal working, I put it in Cron and found that it was failing. As as second step , I removed all the environment dependencies i.e instead of just file.txt, I specified /home/blah-blah/file.txt
I still found the script to be failing still at one step. The step was a data processing tool.
The command i executed was /bin/blah-blah/processing_tool -parameter $INDEX where $INDEX is a variable calculated within the bash script.
Third step was to add the bash profile as source at the beginning of the bash script. Voila!!!! The script started executing perfectly from cron.
My question is why is this happening even after I removed all the environment dependencies from my script. Also I have heard that sourcing a cron job to a bash profile is not recommended. If so, Is there any other way in which I can avoid doing this.
Basicly: Anything started from cron starts with a totally clean slate.
You can make no assumptions whatsoever about the content of environment variables or whichever folder is the current folder at the start of any script run from cron.
Easiest solution:
cd to the desired directory to make sure your path is in the desired location.
source /etc/profile to mak sure you get the system wide environment variables setup.
source ~myuserid/.profile to read your personal environment settings. (~/.profile won't work as that would indicate the cron user.)
Then start executing the actual script.
Of course the approach above requires the cron process to have read access to your home-dir adn it's probably doing a lot more work thatn is actually required.
Slightly more complicated: Figure out which environment variables are required by the script and anything that gets called by the script.
Explicitly export these at the beginning of the cron script.
(P.s. replace /etc/profile and ~myuserid/.profile with whatever are the corresponding files for your shell of choice.)
A cron can be thought of as a separate user. So, this "user" may not "see" or "read" the same files as you do. It is thus essential that all path names etc. be defined in the absolute.
Every script runs within its own process. So, when you run a script, you can change the $SHELL and any other variable within but it will be lost once you get out of it. My guess is that the $INDEX variable computation may have had been computed within the script successfully but its use outside of the script may have failed. Without more information about what job it was, or what you wanted to do, it is hard to tell.
There are two ways to run a cron job:
As root, you can run su -user -c < job > in root crontab.
Sourcing your profile explicitly, as you have done.
You can also set environment variables within the crontab.
As user in the user crontab, you can run it like so: "/home/blah/.profile && myScript"
That said, there HAS to be something in your environment variables (apart from file extensions) that is not present when you run the cron job. You will have to execute that script with -x flag (in bash) and then pore over the output. Using a diff between your environment variables and that of root/cron might be a pointer. Also, check if there are some utilities that are being used in your scripts whose locations are not part of the $PATH variable for cron/root.

How to set environment variable for everyone under my linux system?

Can I have certain settings that are universal for all my users?
As well as /etc/profile which others have mentioned, some Linux systems now use a directory /etc/profile.d/; any .sh files in there will be sourced by /etc/profile. It's slightly neater to keep your custom environment stuff in these files than to just edit /etc/profile.
If your LinuxOS has this file:
/etc/environment
You can use it to permanently set environmental variables for all users.
Extracted from: http://www.sysadmit.com/2016/04/linux-variables-de-entorno-permanentes.html
man 8 pam_env
man 5 pam_env.conf
If all login services use PAM, and all login services have session required pam_env.so in their respective /etc/pam.d/* configuration files, then all login sessions will have some environment variables set as specified in pam_env's configuration file.
On most modern Linux distributions, this is all there by default -- just add your desired global environment variables to /etc/security/pam_env.conf.
This works regardless of the user's shell, and works for graphical logins too (if xdm/kdm/gdm/entrance/… is set up like this).
Amazingly, Unix and Linux do not actually have a place to set global environment variables. The best you can do is arrange for any specific shell to have a site-specific initialization.
If you put it in /etc/profile, that will take care of things for most posix-compatible shell users. This is probably "good enough" for non-critical purposes.
But anyone with a csh or tcsh shell won't see it, and I don't believe csh has a global initialization file.
Some interesting excerpts from the bash manpage:
When bash is invoked as an interactive
login shell, or as a non-interactive
shell with the --login option, it
first reads and executes commands 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.
...
When an
interactive shell that is not a login
shell is started, bash reads and
executes commands from
/etc/bash.bashrc and ~/.bashrc, if
these files exist. This may be
inhibited by using the --norc option.
The --rcfile file option will force
bash to read and execute commands from
file instead of /etc/bash.bashrc and
~/.bashrc.
So have a look at /etc/profile or /etc/bash.bashrc, these files are the right places for global settings. Put something like this in them to set up an environement variable:
export MY_VAR=xxx
Every process running under the Linux kernel receives its own, unique environment that it inherits from its parent. In this case, the parent will be either a shell itself (spawning a sub shell), or the 'login' program (on a typical system).
As each process' environment is protected, there is no way to 'inject' an environmental variable to every running process, so even if you modify the default shell .rc / profile, it won't go into effect until each process exits and reloads its start up settings.
Look in /etc/ to modify the default start up variables for any particular shell. Just realize that users can (and often do) change them in their individual settings.
Unix is designed to obey the user, within limits.
NB: Bash is not the only shell on your system. Pay careful attention to what the /bin/sh symbolic link actually points to. On many systems, this could actually be dash which is (by default, with no special invocation) POSIXLY correct. Therefore, you should take care to modify both defaults, or scripts that start with /bin/sh will not inherit your global defaults. Similarly, take care to avoid syntax that only bash understands when editing both, aka avoiding bashisms.
Using PAM is execellent.
# modify the display PAM
$ cat /etc/security/pam_env.conf
# BEFORE: $ export DISPLAY=:0.0 && python /var/tmp/myproject/click.py &
# AFTER : $ python $abc/click.py &
DISPLAY DEFAULT=${REMOTEHOST}:0.0 OVERRIDE=${DISPLAY}
abc DEFAULT=/var/tmp/myproject

Resources