Shell login - profiles and scripts

From Helpful
Jump to navigation Jump to search
Linux-related notes
Linux user notes

Shell, admin, and both:

Shell - command line and bash notes · shell login - profiles and scripts ·· find and xargs and parallel · screen and tmux ·· Shell and process nitty gritty ·· Isolating shell environments ·· Shell flow control notes

Linux admin - disk and filesystem · Linux networking · Init systems and service management (upstart notes, systemd notes) · users and permissions · Debugging · security enhanced linux · PAM notes · health and statistics · Machine Check Events · kernel modules · YP notes · unsorted and muck

Logging and graphing - Logging · RRDtool and munin notes
Network admin - Firewalling and other packet stuff ·

Remote desktops

This article/section is a stub — some half-sorted notes, not necessarily checked, not necessarily correct. Feel free to ignore, or tell me about it.

This is mostly about bash's behaviour, and tweaking it.

It doesn't touch much on shell programming or syntax.

Files that are sourced, and when

(See also X startup scripts)

Note that the below mostly details the bash / bourne family.

Other shells, say zsh, ksh, csh, tcsh, and others, work differently enough to merit their own summary (By someone who uses them. Details welcome).


  • graphical shells typically do not run login shells, unless forced to (verify)
which often means you don't want to make the distinction of interactive and login yourself
...and put all the stuff you need for interaction in the run-for-interactive part, which for bash means bashrc
  • assume profile gets run once per shell, while bashrc also gets run on subshells (if you use them)
(you may care to avoid redefinitions sometimes)
  • scripts aren't shells, and should explicitly source the environment they need, or may react differently when run by other people, from batch queues, services, cron, compilation environments, etc.
often they need very little
in some cases, environment modules are the thing that keeps you sane
BASH_ENV is a nice tool for some cases
  • ssh can be an exception case, at least under the covers. But usually does what you want.

Context: Login shell versus interactive shell

Each shell has two properties relevant to this discussion:

  • login or non-login
  • interactive or non-interactive

These are two distinct properties, so there are four combinations, yet in most practice you consider one of two or three cases:

  • first on a tty (login+interactive)
'interactive login shell', 'login shell', 'initial login shell' usually refer to this.
those that are first on a shell, including hardware (text-mode) terminals
those specifically requested to be login, e.g. bash -l or bash -login (e.g. VNC's .Xstartup often does this)
shells implicitly created when switching users with su - username (su username gives a non-login shell), sudo -i
(note: sudo's shorthand for login is -i, su's is -l. I consider that stupidly confusing.)
  • not first on a tty (non-login interactive)
shells launched by the user
graphical terminal programs invoke these by default(verify)
those specifically requested to be interactive, e.g. bash -i
su, unless you explicitly use - / -l / --login
  • non-interactive, e.g. scripts
aside from scripts themselves, examples are:
bash run from a shell script: that subprocess is always considered a non-interactive shell (even if it can access its tty) (verify)
commands invoked via bash -c (verify)
these do not implicitly read any configuration files (verify)
(can be login, typically is not - mostly related to ssh[1])

Footnotes to that:

  • GUI terminals
many tend to start non-login interactive shells, apparently arguing that you start one login shell with the display manager, you still get the "once per graphical session" behaviour (verify)
others choose to run login shells in each terminal window, arguging that a terminal window is a session, unrelated to the graphical one (also since graphical startup has its own rcfiles, e.g. xsession)
  • SSH
allocates a pseudoterminal, so is considered login
somewhat manually imitates a local bash (and bash may be specialcased)
so usually it's exactly the same, but sometimes it needs coaxing to behave quite the same as local shells
  • tmux starts login shells, screen does not ( default, you can set these)
tmux's idea seems to be that new shells should more predictably pick up new config this way
  • some security-aware things like sudo and cron specifically avoid sourcing any of that

Checking for login / interactive shell

This article/section is a stub — some half-sorted notes, not necessarily checked, not necessarily correct. Feel free to ignore, or tell me about it.

For bash, a quick copy-paste:

shopt -q login_shell   && echo 'Login' || echo 'Not login'

[[ $- == *i* ]] && echo 'Interactive' || echo 'Not interactive'


For bash,

shopt will mention either login_shell on or login_shell off
In a test, shopt -q is often nicer - it tells you in the return code, and is silent.

Doing this in a more portable way (beyond bash) is a bit harder [2]

Apparently a leading - in the $- value also indicates login?(verify)


In bash, look whether special bash variable $- contains i, e.g.

case $- in
   *i*) echo 'Interactive' ;;
     *) echo 'Not interactive' ;;

In many shells, checking whether there is a variable like PS1 also tends to work well

Distinctions between .bashrc and similar files

Quick and dirty summary

Note that while some details below assume bash, some go into details beyond bash.

So when it says "bashrc", you may want to read it as "the applicable shell(-specific) rc files"

  • PAM hooks tend to happen before the below
but is only for environment, not for scripts
and only happens at auth time(verify)
  • profile should set up everything that gets initialized once and may be used down the line
once per terminal, at login time.
  • bashrc is read on "an interactive shell that is not a login shell"
  • in practice it's fairly common to put everything to be run for all shells and subshells into .bashrc, to happen at each shell invocation.
and to have profile explicitly source bashrc if it exists.
  • The distinction betweenthe last two is -- roughly -- login shells versus interactive shells.
  • some GUI terminals may (unlike historical terminals) specifically avoid a login shell
  • Scripts are different
  • SSH is different (can be, anyway)
  • cron is different


often want to put things in /etc so that all users inherit said configuration.
...and should check exactly what the distro does in terms of what is sourced from what
/etc/profile.d/ is quite convenient if your disto is set up to use it

For users, putting stuff in your personal rc files (e.g. ~/.bashrc for bash users) is the easiest.

(though technically extending PATH is best put in your profile, to avoid it filling up with duplicates in subshells)

Practical setup

More details

PAM /etc/environment

When PAM is configured to do this, it reads and sets the contents in the environment, before loading your shell.

This means

  • it's a config file parsed for its key=value lines, not a sourced/executed script
  • it's actually quite useful to set environments in all shells, including exotic ones.
  • it only applies when authenticating (using PAM)
The next graphical login will (verify)
A ssh (e.g. to localhost) will.
Running bash (-i or -l) won't.
Running an xterm (-i or -l) won't (verify)

While for larger scales it can be a useful tool, and sometimes a more secure one, it's not widely used on small setups because it's yet another thing to consider (and can only set env).

Things called profile

Things called profile are intended to be a sourced only by login shells

  • ~/.profile for user-specific stuff (but see the next point)
  • /etc/profile for system stuff

profiles for for specific shells

  • bash_profile (and other specific-shell profiles) are intended for run-once-per-terminal things
...that need shell-specific syntax/features

Sometimes with footnotes. For example, bash looks for ~/.bash_profile, ~/.bash_login, ~/.profile - in that order and uses only the first one of these it finds

That allows options like

only having a .profile, shared between some shells (that are still similar enough to share its syntax)
having ~/.bash_profile
that has some of its own things
and then explicitly sources the still-shared .profile
and/or specific .bashrc (in that order)
..but frankly, try to keep this as simple as possible
arguably most things belong either in .profile or .bashrc

Side note: most shells do not have shell-specific profile file in /etc


If /etc/profile.d/ exists, it will be explicitly sourced from /etc/profile

This is a relatively recent convention - but also common because it's required by LSB.

(This (and other .d things) is particularly convenient for package management, which can now add/remove its parts by placing/remocing a file, rather than a bunch of lines that will be harder to add/remove from a singular file.)

Sourcing them from bourne-family amounts to:

for i in /etc/profile.d/*.sh ; do
    if [ -r "$i" ]; then
        . $i

Note that since it specifically looks for .sh, and e.g. csh looks for .csh, this also helps organize shell-specific things a bit.

Note that sh still means "any /bin/sh", including minimal ones like dash.

bashrc and similar

bashrc is intended for every interactive bash shell (and gets sourced each subshell)

  • ...and by default not sourced from login shells
    • ...but most distros add a source ~/.bashrc to the relevant profile (~/.profile or ~/.bash_profile) because it's useful for whatever they should share
    • yet some (e.g. OSX's Terminal) do not
  • /etc/bash.bashrc for system stuff
  • ~/.bashrc are intended for interactive shells

In practice:

  • Many distros set up /etc and account skeletons (/etc/skel) where: (verify)
~/.profile OR ~/.bash_profile explicitly sources ~/.bashrc
/etc/profile OR ~/.bashrc explicitly sources /etc/bashrc (verify)
  • profile stuff (included by login shells) tends to be the most universal
  • bashrc will be there for all interactive shells
so aliases, EDITOR, prompt, etc. make sense here
  • For "add to environment" the profile/bashrc distinction matters less, because it tends to get inherited everywhere you want anyway
though PATH=$PATH:more is best placed in profile to avoid repeated adds

  • the applicable things in /etc/ will often specifically not do most of their work when they happen to be sourced from scripts.
via something like [ -z "$PS1" ] && return, or parts wrapped by if [ "$PS1" ] theory you could have a specific section in there for just scripts.
It's generally not best practice, though, in part because it means that file needs more protection (e.g. avoid output unless [[ $- == *i* ]]. Also because:

  • scripts should never count on environment, at least not to run at all
so if you need specific environment, do it explicitly. Usually sourcing is best for your sanity
that includes PBS, MPI stuff
in some cases, a specific /etc/profile.d file might be well suited

Misc notes:

  • cron explicitly uses a restricted environment (and often runs scripts), so usually none of this applies

(which is a good idea for safety)

so explicitly include/source any env you need

  • You usually don't need to bother with login and logout rcfiles at all
  • .login
apparently the C-shell convention, practically the same as .profile
to keep things simpler, avoid unless you need it
  • .bash_login was apparently an early attempt to be more compatible with C-shell - that didn't really work out and you can ignore it


Some related mechanics

Hardware terminal login

Just for context, hardware terminals are handled by a process historically called getty [3]

getty is the thing that asks for username (just the username), and then executes login telling it that username.

login hooks in the rest - checks whether you need a password, and if so asks you for it (presumably these days hooks into PAM(verify)), and if satisfied hands off to your configured shell (often bash).

Bash does its startup thing, as mentioned above. In this case as a login shell (pretty much always, in this path?(verify)).


Much like the getty case, but with sshd taking the place of getty, and with some of its own behaviour.

sshd invokes bash as a login shell (at least by default), which is what you'd expect.

ssh agent, if any, should be set up in bash_profile

If a command is given, it is executed instead of a login shell, not within a shell, so none of these apply.

Some added details:

  • the ssh client may be sending some environment from the client side - see SendEnv and AcceptEnv [4]

On a few programs having more requirements

While relatively rare, and a little unhandy, there are a few things that run themselves remotely over SSH, and as such expect it to behave to certain restrictions.

rsync is one such example. if your profile spits out things,

On cron

Basically, these

are run as scripts (not under shells, so assume no implicit profile and rc sourcing)
have system environment stripped for security reasons (verify)

If you want to include a certain shared environment, consider BASH_ENV

See also Automation,_remote_management,_configuration_management#Cron_and_environment

On graphical login

Tends to have its own rc files, making the distinction relatively moot -- except if you also want the same thing in SSH logins.

X terminals within graphical logins means the window manager creating a process (e.g.) via forking, which is relevant in that it inherits environment (like DISPLAY).

The shell here will often not be a login shell, due either to historic reasons, or to specific choice by the terminal emulator.

You can configure most it to start login shells.



If you want to know what exactly your specific shell is doing and sourcing, you can watch it open files with something like

strace -f -e trace=file  bash -l -c echo  2>&1 | grep open | egrep -v '(/lib|/proc|locale|[.]so|resumed)'
the -l in this example specifically tests a login shell (-c echo just makes the new shell quit after it does nothing worth mentioning)
the greps take out most stuff you're probably not interested in

See also (files)

  • this for more bash details.
  • this for more general details.
  • this for details for other shells.

TODO: read:

Config files

Potentially useful fragments for your bashrc and bash_login

Note: The difference between bashrc and bash_login is that bashrc is for interactive shells (including xterms, I believe), and the second for login shells (including screen windows).

In practice you may want to put things into both, or create a script of common things that you call from both.

grep results in color

When it is not the system default, you can get grep to output colors when it is the main command (not when used in a pipe, based on autodetection), using:

export GREP_OPTIONS='--color=auto'

If you want it even in pipes you can set it to always. Can be nice e.g. for less, but not everything you pipe into understands the escape codes. Arguably always is best used in aliases, not as a default.

You can change the colors it uses using GREP_COLORS (or the deprecated GREP_COLOR), in which you can set the color of various different parts of the grep results; see the man page.

History stuff

Searching the history

CtrlR is an interactive search, from most recent to older (press CtrlR again).

Note that if you press enter you immediately execute the command; you often want to press an arrow key or Tab instead so that you can edit it first.

For a less interactive thing, there's history

Cleaner history

You can specify things that should not be logged, which can be handy when arrow-upping for recent interesting commands as well as using Ctrl-R to search it.


export HISTIGNORE="&:cd:mc:top:htop:svn st:exit:mutt:ls:[bf]g *|[bf]g"

In this example:

  • The & means 'matches the previous line', which avoids adjacent duplicates from being logged.
  • most commands here are specified to be matched exactly, so when you use them with arguments they are still saved.
  • ...whereas bg and fg are explicitly ignored for all cases.


  • is a colon-sperated list of options that further controls how and what to save
  • ignoreboth shorthand for ignorespace:ignoredups
  • ignorespace Lines that start with a space are not saved
  • ignoredups causes lines matching the previous history entry to not be saved. A value of
  • erasedups previous lines matching current line are removed from history before the current line is saved.

Longer history

Note that the meaning of values other than numbers ≥ 0 has changed somewhat over time, read your man bash


  • the amount of recent history entries that a bash process keeps
  • default: 500
  • setting unlimited history depends a little on bash version, so check man bash to be sure (but it'll be negative or blank)
  • 0 means don't save
  • my man page saus "Numeric values less than zero result in every command being saved on the history list (there is no limit).


  • the amount of a specific shell's own history, in lines, to write to HISTFILE (when the running shell exits)
  • default: value of HISTSIZE, unless set
  • 0 means truncate to empty file
  • my man page saus "Non-numeric values and numeric values less than zero inhibit truncation"


  • the file that the the running shell writes its own history to (when it exits)
  • default: ~/.bash_history
  • note: unsetting means 'don't save this shell's history'

histappend (shell option) - append to central history instead of replacing it

The basic behaviour:

  • On startup, bash loads HISTFILE
apparently the most recent HISTFILESIZE lines (verify)
  • each line is saved to in-memory history (adhering to HISTCONTROL and HISTIGNORE)
  • When it exists, bash
    • writes its own in-memory history to HISTFILE (overwrite/append, according to histappend)
    • then truncates that file to have at most HISTFILESIZE lines (unless HISTFILESIZE is not set)

You can force bash to write to and update from HISTFILE using history -a; history -n

(I doubt there is file locking going on, so PROMPT_COMMAND=’history -a; history -n’ might lead to occasional clobbering(verify))

Note on export, setenv, etc

This article/section is a stub — some half-sorted notes, not necessarily checked, not necessarily correct. Feel free to ignore, or tell me about it.

In bash, export replaces a variable in the current environment. Other shells may use setenv or set.

#usually written as:
export PATH=$PATH:/bla/bla

#which is equivalent to a local set followed by an explicit export
export PATH

Export means that it will inherit (get copied) to subenvironments. (I remember there being some pesky details. I should check up on them.)

Useful things to export include:

  • path to look for things to execute (PATH)
  • almost everything in profile/bashrc files, e.g.
program defaults (e.g. GREP_COLOR=auto, LESS="-aXz-5j3", etc.)
programs' home directories (eg. GMT_HOME, GRASS_HOME, JDK_HOME)
an added directory to have private libraries (LD_LIBRARY_PATH)
compiler options (CFLAGS, CPPFLAGS, LDFLAGS for adding additional include and library paths when you want to compile things)

Setting PATH

Fairly simple. To add /etc/local/bin/myprogrambins/ and your $HOME/bin to the PATH, for example, do:

export PATH=$PATH:/etc/local/bin/myprogrambins/:$HOME/bin

This preserves the current path and appends the new one. It is a safe default to add to the end of this list in case of name collisions; the first match in the list will be used. This can be exploited for good (perhaps sneaking in colorgcc as default gcc behaviour) and evil (many other cases).

It is not advised to do:

export PATH=$PATH:.

...just to get windows/DOS-like behaviour in that you can run executables from the current directory without having to append a ./. You may get randomly strange directory-dependent behaviour. Doing this ranges from useful-when-careful to annoying to a potential security problem.

Terminal type

See terminal type

setting your prompt

(See also e.g. [5])

You usually see what is set in PS1, and you see PS2 if you have multi-line strings and the sort. You're not likely to ever see PS3 or PS4.

It looks like the internal prompt variables (user, host, working directory, date, time, and things like \$; see PROMPTING in man bash) and escapes are evaluated after each shell command you give, while not everything is, and possibly not under all conditions. See the PROMPT_COMMAND for a workaround.

Funky things

Note on using/printing escapes

You can use escape sequences to give ANSI color commands, set the xterm title, move/save/restore the cursor, command a wrapping screen, and other such things.

Note on \[ and \]

Bash and some other shells (which?(verify)) treat \[ as "from here to a \] I shall interpret escapes", so that's where you usually put them.


  • These ranges are not counted as printable characters (shouldn't be), which matters in line wrapping behaving sensibly.
  • When you take take values from somewhere to be displayed as part of the prompt (expansion of hostname, cwd, etc.), you want values outside such a range, both for correct wrapping and to avoid strange interactions. If you want to do something to such values, add a sequence before and one after.
  • There are escape sequences that contain ] and those that contain [. Yes indeed, that's bloody confusing :)

Note on \e versus \033

\e is a shell-specific (probably bash-specific (verify)) way of saying "I want a literal ESCape control character here."

A more compatible and slightly harder to read alternative is \033 (ESC is 033 in octal, 1B in hex, 27 in decimal)

Similarly, \a is an alternative to \007 (BEL).

If you want to experiment with escapes, you may want to know that you can force echo to interpret escapes by using -e, and sometimes -n, 'don't append newline' ). Note however that echo doesn't know \[ and \].

Funky things: Setting the shell's title

ESC, ], 0, ;, titlestring, BEL

The text inside is interpreted by teminals that understand titles, such as most graphical terminals.

For example, you could prepend this to your real prompt:


for something like

Funky things: setting the screen window's title

ESC, k, titlestring, ESC, \

This is the title you see when you list the screens (using Contol-a "), and can set using Control-A Shift-A.

For a prompt, and with escaping inside a "-quoted string, this works out to, for example:

\[\033k \u, in \w \033\\\]

When you also want to make screen change the X window(-as-in-graphical-window) title according to which screen you are in, you want to tell screen to use its hardline tricks, via .screenrc. See the basic example at screen.


Note that since some older and barer terms may respond badly you should put fancy stuff on a condition, by using something like the following in your bash startup:

case $TERM in
      PS1="\[\033]0;\u on \H\007\]\[\033k\]\w\[\033\\\\\]\u@\H:\[\033[1;37m\]\w\[\033[37m\] \$ "
      PS1="\[\033]0;\u on \H, in \w\007\]\u@\H:\[\033[1;37m\]\w\[\033[37m\] \$ "
      # avoid funky things when you are not sure about the capabilities:
      PS1="\u@\H:\w \$ "

You may want \w (working directory) if you come from windows/DOS or perhaps regardless, though you get used to the non-clutter and remembering where you are. I just decided this format may be useful as it mimics the format scp uses, so it could be useful for copying things about.

If you want to set both screen's window titles and set the window window title, you could e.g.

  • 'user on host' in the window title via PS1
  • set the working directory as the screen window title,
  • use screen's hardstatus feature to prepend <something like>'[0: /var]' (that is, '[screenwindownum: screenwindowtitle]' ) to the window title.

The result is that screen shows "/var/www" and the whole window shows "[0: /var/www] root on sparrow"

You could have something like:

Extra evaluation

Setting PROMPT_COMMAND allows you to evaluate/execute something before every command -- though note that this is potentially slow.

For example, you could do PROMPT_COMMAND="echo -n 'Groups: '; id -G -n"

A common use is to change PS1 every prompt, for example...

  • showing the current directory's size
  • make the prompt/title change with variables that aren't part of the \u-style thing bash itself replaces
  • showing the last command's return code ($? in bash)
  • using whoami to show things in red if you're root (though it's simpler to have root's profile/bashrc could just set this explicitly)

You can set the PROMPT_COMMAND variable to

  • a string to evaluate, including the path to a script
  • a bash function, so you can write potentially involved code, such as the following excessive example:
promptFunc() { 
  case $TERM in
      PS1="\[\033[0;32m\][\D{%a, %H:%M}] \[\033[1;34m\]\u@\H\[\033[0m\]:\[\033[1;37m\]\w\[\033[0m\] " ;; 
      PS1="[\A] \u@\H:\w " ;;

  # size of immediate entries (i.e. recursive)
  DIRSIZE=$(ls -l | awk '/^[dlpscb\-][rsS\-][wsS\-][xtT\-]/{totalsize+=$5} END{printf("%d",totalsize)}');
  if [ $DIRSIZE -ge 1073741824 ]; then
    if [ $DIRSIZE -ge 1048576 ]; then
      if [ $DIRSIZE -ge 1024 ]; then
  PS1="${PS1}($DIRSIZE) "

  #append a red [root] if we are root
  if [ `whoami` == "root" ]; then 
    PS1="$PS1\[\033[31m\][root]\[\033[0m\] "

  PS1="${PS1}\$\[\033[0m\] "


  • the fact that ls -l will happen all the time will make changing to directories with very many entries rather sluggish.
  • it might be more robust to do the dirsize based on find ./* -prune -type f -printf "%s\n"

Colored text

See Escaping_and_delimiting_notes#ANSI_color

See also

setting DISPLAY

Note that it is easy to mess with other logic to this effect, from the system or from ssh -X.

keybinds and convenience settings


aliases, functions

See some examples at Command_line_and_bash_notes#Shell_aliases_and_functions

Arguably some should be system-wide scripts.

Colored ls

GNU ls listens to

  • environment variable LS_OPTIONS for whether/when to use colors, probably export LS_OPTIONS='--color=auto'
  • environment variable LS_COLOR for which colors to use

You may prefer to use dircolors for easier debugging of the contents of LS_COLOR. dircolors reads its own configuration file and produces the sh-style/csh-style environment set line you'ld want in your profile.

When fiddling with dircolors and you want to set it within the current shell, you can do something like: eval `dircolors -b /etc/dircolors`
(the -b is for bash, -c is for csh. This is purely about how to set the environment variable)

There will often be some logic in the system-wide bashrc (or comparable for other shells) that calls dircolors and evaluates its output. It may prefer


and fall back to: (verify)


You can find defaults and examples in various places.

(See also LS_OPTIONS for other ls formatting options. I like -p)


Potentially useful scripting lines

See also Command_line_and_bash_notes#Shell_conditionals_and_scripting for more general sh/bash syntax notes

"stop now if we don't have this command"

tl;dr you want something like:

command -v pidof >/dev/null || { echo "cannot find pidof, aborting."; exit 1; }

While which seems the obvious choice, it is not as well behaved as you think.

There are other things that effectively do the same, that are shell builtins (so also avoid a process). If you want to support at least bash and sh, then command -v seems best behaved (defined by POSIX).

(Thanks to this stackoverflow summary)

error/default value if not specified

See Command_line_and_bash_notes#Error_if_not_set

Other shell tricks

special home directory files


Various mail daemons can be configured to look for this file when email is delivered to an account. If the file exists, the contents are treated as the mailboxes and/or email addresses to deliver the mail for.

It is faily common to forward(-and-delete) to a single remote email address, though you can also leave a copy in the account, etc.

See also:


This article/section is a stub — some half-sorted notes, not necessarily checked, not necessarily correct. Feel free to ignore, or tell me about it.

You can suppress the login banner by placing a file named .hushlogin in your home directory.

This seems a convention followed by various motd style things, but not by everything.

Other bash notes

Special variables

This article/section is a stub — some half-sorted notes, not necessarily checked, not necessarily correct. Feel free to ignore, or tell me about it.
$0	           script name
$1, $2, $3, etc.   first argument, second, third, etc. You may prefer ${1}, ${2}, etc.
$*                 List of all variables entered at the command line, as a string
$@                 List of all variables entered at the command line, as an array
                   (though when not quoted like "$@", $@ will behave like $*)(verify)
$#                 number of command line arguments
$$                 PID of the current shell
$!                 PID of the most recent (background?) command
$?                 Exit code of the last (foreground/pipe) command 
$-                 current shell options

The difference between $* and "$@" doesn't always matter. (and note $@ without quotes behaves like $*)

It e.g. doesn't when you're just printing them.

When iterating over them, however, "$@" will be the actual arguments, so can contain spaces

There are further footnotes to use of $* / "$@" on commands.(verify)

You may further care about Command_line_and_bash_notes#Conditional_replacement

exit from sourced file

use return

e.g. if [ -n "___SOFTSETONCE" ]; then return; fi