Bash: Output all aliases and functions in a script - bash

Take the following script:
shopt -s expand_aliases
set -f
result=$(compgen -A function)
echo $result
When running it outputs all my custom bash functions:
mp3gain pkg-pkgbuild-download quote quote_readline restart standby turnoff turnoff-timer youtubeConvert
However, when slightly changing the script to output aliases, the output is empty:
shopt -s expand_aliases
set -f
result=$(compgen -A alias)
echo $result
Yet it is not empty if I run compgen -A alias directly.
My aliases are stored in ~/.bash_aliases and my functions in /.bash_functions. Both are sources in ~/.bashrc:
# Functions
if [ -f ~/.bash_functions ]; then
. ~/.bash_functions
fi
# Aliases
if [ -f ~/.bash_aliases ]; then
. ~/.bash_aliases
fi
What am I missing here?

I'd be willing to wager that the aliases are not sourced. This can be verified running this simple script:
#!/bin/bash
alias
If there is no output, the aliases are not sourced. Hence, that's why compgen returns an empty list when put in a script (non-sourced aliases) but works fine when run manually in a shell with sourced aliases.
Solution: put "source ~/.bash_aliases" near the top of your script to make sure they are invoked before running.

Related

How to `command file.txt OR another_file.txt` in CLI (bash/zsh)

Is it possible to run a command with either file.txt as an argument or, if file.txt doesn't exist, with another_file.txt?
To make my request more realistic, I'd like to run VSCode with workspace file by default or, if workspace file doesn't exist, with . (current folder), something like:
code *.code-workspace OR .
If *.code-workspace exists, then the command should be equivalent to code *.code-workspace, otherwise the commend should be equivalent to code .
Is it possible in bash or zsh? (I'm using zsh + oh-my-zsh)
In bash you can use nullglob to expand wildcards (like *) to nothing if there is no match. Then put everything into an array and retrieve the first entry. Note that $array is the same as the first array entry ${array[0]}.
#! /bin/bash
shopt -s nullglob
files=(*.code-workspace .)
code "$files"
Above code starts code firstMatchOf.code-workspace if there is a file ending with .code-workspace and . if there is no such file.
For zsh you can do the same by replacing shopt -s nullglob with setopt null_glob.
Note that above approach only works with wildcards. files=(a b); code "$files" will call code a even if a does not exist. Here you could use the following function instead, which should work in every case:
#! /bin/sh
firstExisting() {
set -- "$#" . # ensure termination
while ! [ -e "$1" ]; do
shift
done
printf %s\\n "$1"
}
Usage example:
code "$(firstExisting *.code-workspace .)"
or just
code "$(firstExisting *.code-workspace)"
… as . is the default in case none of the arguments existed. shopt -s nullglob is not needed here.

Why `~/.bashrc` is not executed when run docker container?

I have a docker file as below. launch.sh is the entry point in this docker image.
FROM ubuntu:16.04
USER root
RUN apt-get update && apt-get install -y \
curl \
vim \
net-tools \
git \
iputils-ping \
wget
RUN apt-get install -y python
RUN apt-get update && apt-get install -y gcc g++ make libgflags-dev libsnappy-dev zlib1g-dev libbz2-dev liblz4-dev libzstd-dev
RUN curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.33.11/install.sh | bash
ENV NVM_DIR /root/.nvm
RUN . $NVM_DIR/nvm.sh && \
nvm install 7.9.0 && npm install -g npm#5.6.0
ADD ./Docker/launch.sh /workspace/
CMD ["/bin/sh", "/workspace/launch.sh"]
The content of launch.sh is:
#!/bin/bash
cd /workspace/demo
npm install
node index.js
when I run the docker container: docker run IMAGE_NAME, I got this error:
npm: not found
node: not found
The node in this image is managed by nvm which has been installed and its script has been set on /root/.bashrc file. But I don't know why it can't find the nodejs commands. But if I run the container by docker run -it IMAGE_NAME bash, then manually run workspace/launch.sh command, everything works fine. It seems the ~/.bashrc is not executed when run the image. How can I let the container source .bashrc?
The content of /root/.bashrc is:
# ~/.bashrc: executed by bash(1) for non-login shells.
# see /usr/share/doc/bash/examples/startup-files (in the package bash-doc)
# for examples
# If not running interactively, don't do anything
[ -z "$PS1" ] && return
# don't put duplicate lines in the history. See bash(1) for more options
# ... or force ignoredups and ignorespace
HISTCONTROL=ignoredups:ignorespace
# append to the history file, don't overwrite it
shopt -s histappend
# for setting history length see HISTSIZE and HISTFILESIZE in bash(1)
HISTSIZE=1000
HISTFILESIZE=2000
# check the window size after each command and, if necessary,
# update the values of LINES and COLUMNS.
shopt -s checkwinsize
# make less more friendly for non-text input files, see lesspipe(1)
[ -x /usr/bin/lesspipe ] && eval "$(SHELL=/bin/sh lesspipe)"
# set variable identifying the chroot you work in (used in the prompt below)
if [ -z "$debian_chroot" ] && [ -r /etc/debian_chroot ]; then
debian_chroot=$(cat /etc/debian_chroot)
fi
# set a fancy prompt (non-color, unless we know we "want" color)
case "$TERM" in
xterm-color) color_prompt=yes;;
esac
# uncomment for a colored prompt, if the terminal has the capability; turned
# off by default to not distract the user: the focus in a terminal window
# should be on the output of commands, not on the prompt
#force_color_prompt=yes
if [ -n "$force_color_prompt" ]; then
if [ -x /usr/bin/tput ] && tput setaf 1 >&/dev/null; then
# We have color support; assume it's compliant with Ecma-48
# (ISO/IEC-6429). (Lack of such support is extremely rare, and such
# a case would tend to support setf rather than setaf.)
color_prompt=yes
else
color_prompt=
fi
fi
if [ "$color_prompt" = yes ]; then
PS1='${debian_chroot:+($debian_chroot)}\[\033[01;32m\]\u#\h\[\033[00m\]:\[\033[01;34m\]\w\[\033[00m\]\$ '
else
PS1='${debian_chroot:+($debian_chroot)}\u#\h:\w\$ '
fi
unset color_prompt force_color_prompt
# If this is an xterm set the title to user#host:dir
case "$TERM" in
xterm*|rxvt*)
PS1="\[\e]0;${debian_chroot:+($debian_chroot)}\u#\h: \w\a\]$PS1"
;;
*)
;;
esac
# enable color support of ls and also add handy aliases
if [ -x /usr/bin/dircolors ]; then
test -r ~/.dircolors && eval "$(dircolors -b ~/.dircolors)" || eval "$(dircolors -b)"
alias ls='ls --color=auto'
#alias dir='dir --color=auto'
#alias vdir='vdir --color=auto'
alias grep='grep --color=auto'
alias fgrep='fgrep --color=auto'
alias egrep='egrep --color=auto'
fi
# some more ls aliases
alias ll='ls -alF'
alias la='ls -A'
alias l='ls -CF'
# Alias definitions.
# You may want to put all your additions into a separate file like
# ~/.bash_aliases, instead of adding them here directly.
# See /usr/share/doc/bash-doc/examples in the bash-doc package.
if [ -f ~/.bash_aliases ]; then
. ~/.bash_aliases
fi
# enable programmable completion features (you don't need to enable
# this, if it's already enabled in /etc/bash.bashrc and /etc/profile
# sources /etc/bash.bashrc).
#if [ -f /etc/bash_completion ] && ! shopt -oq posix; then
# . /etc/bash_completion
#fi
export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" # This loads nvm
[ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion" # This loads nvm bash_completion
Each command runs a separate sub-shell, so the environment variables are not preserved and .bashrc is not sourced (see this answer).
You have to source your script manually in the same process where you run your command so it would be:
CMD source /root/.bashrc && /workspace/launch.sh
provided your launch.sh is an executable.
As per documentation exec form you are using does not invoke a command shell, so it won't work with your .bashrc.
Edit:
BASH wasn't your default shell so
CMD /bin/bash -c "source /root/.bashrc && /workspace/launch.sh"
was needed in order to run your script.
If you want yo set your shell as BASH by default, you can use SHELL instruction as described in documentation, e.g.:
SHELL ["/bin/bash", "-c"]
None of the existing answers accurately answer the title question: Why ~/.bashrc is not executed when run docker container?
There are two things to be aware of:
Use login shell
According to the bash man page:
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.
Therefore, in order to have .profile/.bashrc read automatically upon invocation of bash, it is necessary to invoke bash with the --login or -l option.
You can do this in a couple ways:
1. Set the shell to include -l option. For example,
SHELL ["/bin/bash", "-l", "-c"]
2. Invoke -l for specific commands using the exec form of RUN:
CMD ["/bin/bash", "-l", "-c", "/workspace/launch.sh"]
Note top of .bashrc
From the man page above, we know the order in which profile files are searched and loaded. If you look at /root/.profile you may see something like this:
# ~/.profile: executed by Bourne-compatible login shells.
if [ "$BASH" ]; then
if [ -f ~/.bashrc ]; then
. ~/.bashrc
fi
fi
mesg n 2> /dev/null || true
This is how ~/.bashrc gets source for a bash shell. Therefore, we can expect ~/.bashrc to be sourced when the bash shell is used.
However, look carefully near the top of your .bashrc file:
# If not running interactively, don't do anything
[ -z "$PS1" ] && return
This means that effectively the remaining contents of .bashrc are ignored except for interactive shells.
One answer suggests using the -i option of bash to invoke an interactive shell. This does work because the environment variable PS1 is set for interactive shells, and therefore .bashrc continues.
However, perhaps you don't want an interactive shell. In this case, there are a few options:
1. Comment out the return line. You can use something like this in your Dockerfile:
RUN sed -e '/[ -z "$PS1" ] && return/s/^/#/g' -i /root/.bashrc
This modification to .bashrc will prevent its early exit from non-interactive invocations.
2. Move the nvm setup to .profile. Move the last three lines of your .bashrc file to .profile so they're executed unconditionally.
3. Manually source .bashrc. As other answers have already noted, you can certainly manually source .bashrc as needed, as in,
RUN source /root/.bashrc && /workspace/launch.sh
Observe that much of the content of .bashrc makes the most sense for interactive shells and is usually unnecessary otherwise, which may make option 2 above the most appealing.
with CMD and shell form
CMD /bin/bash -i "/workspace/launch.sh"
Edit
should also work with ENTRYPOINT and and using exec form using
ENTRYPOINT ["bash","-i","/workspace/entrypoint.sh"]
I believe the -i flag works in the intended way, the .bashrc file is used as intended, the other solutions did not work for me, the .bashrc file was never used
solution may not be ideal for everyone, with the -i flag the program may prompt for user interaction
ps: I used docker create and docker start -i "container name"
You can add source /path/to/bashrc in launch.sh and change the CMD to the following instead of changing to bash through CMD itself:
CMD ["/workspace/launch.sh"]
Alternatively, You can do the following in your Dockerfile instead of depending on bashrc
ENV NVM_DIR /root/.nvm
ENV NODE_VERSION 7.9.0
ENV NODE_PATH $NVM_DIR/v$NODE_VERSION/lib/node_modules #Ensure that this is the actual path
ENV PATH $NVM_DIR/versions/node/v$NODE_VERSION/bin:$PATH
RUN . $NVM_DIR/nvm.sh && \
nvm install $NODE_VERSION && npm install -g npm#5.6.0

Is there a difference between /etc/profile and a bash init file?

I am trying to extend my bash history size from 1000 commands to 10000 commands.
I am trying to follow this tutorial to extend my bash history from 1000 commands to 10000. In the first paragraph, it says to append the following three lines to my 'bash init.'
export HISTCONTROL=erasedups
export HISTSIZE=10000
shopt -s histappend
Google lead me to the bash beginner guide and I can't read it, since Bash isn't my first language. I think the following excerpt answers my question, but I'm not sure.
When invoked interactively with the --login option or when invoked as sh, Bash reads the /etc/profile instructions. These usually set the shell variables PATH, USER, MAIL, HOSTNAME and HISTSIZE.
Questions I have:
Am I reading this right when I assume that /etc/profile is the same as a bash initialize?
How can I test if this worked? /etc/profile currently looks like this:
export HISTSIZE=10000
shopt -s histappend
# System-wide .profile for sh(1)
if [ -x /usr/libexec/path_helper ]; then
eval `/usr/libexec/path_helper -s`
fi
if [ "${BASH-no}" != "no" ]; then
[ -r /etc/bashrc ] && . /etc/bashrc
fi
Update: putting those commands in the bashrc didn't seem to do anything, but following this add timestamps to bash history tutorial, I put the commands in /etc/bashrc . My history now has timestamps. Is it safe to assume that .bash_history now saves 100000 commands as well?
Bash may read several different files. Since these are bash specific options that don't work for sh, you should put them in ~/.bashrc and make sure you have a line source ~/.bashrc in ~/.bash_profile.
You can test it by opening a new terminal and running echo $HISTCONTROL and shopt histappend to see whether they have the expected values ("erasedups" and "on").

Including/Importing aliases and functions for both bash and zsh

I have a directory of functions and aliases I'd like to include for both bash and zsh terminal invocations (So I don't need to put every function and alias into every separate script and to facilitate organization/tidying of .rc files)
What I've tried so far hasn't worked. Just setting this out for further suggestions.
For zsh I'm using
if [ -d ~/.zsh.d ]; then
for i in ~/.zsh.d/*.sh; do
if [ -r $i ]; then
. $i
fi
done
unset i
fi
Should work for bash, too.

ssh command execution doesn't consider .bashrc | .bash_login | .ssh/rc? [duplicate]

This question already has answers here:
Why aliases in a non-interactive Bash shell do not work
(4 answers)
Closed 6 years ago.
I am trying to execute a command remotely over ssh, example:
ssh <user>#<host> <command>
The command which needs to be executed is an alias, which is defined in .bashrc, e.g.
alias ll='ls -al'
So what in the end the following command should get executed:
ssh user#host "ll"
I already found out that .bashrc only gets sourced with interactive shell, so in .bash_login I put:
if [ -f ~/.bashrc ]; then
. ~/.bashrc
fi
and I also tried to define the alias directly in .bash_login.
I also tried to put the alias definition / sourcing of .bashrc in .bash_profile and also in .ssh/rc. But nothing of this works.
Note that I am not able to change how the ssh command is invoked since this is a part of some binary installation script. The only thing I can modify is the environment. Is there any other possibility to get this alias sourced when the ssh command is executed? Is there some ssh configuration which has to be adapted?
From the man pages of bash:
Aliases are not expanded when the shell is not interactive, unless the expand_aliases shell option is set using shopt
There are a couple ways to do this, but the simplest is to just add the following line to your .bashrc file:
shopt -s expand_aliases
Instead of:
ssh user#host "bash -c ll"
try:
ssh user#host "bash -ic ll"
to force bash to use an "interactive shell".
EDIT:
As pointed out here about non-interactive shells..
# If not running interactively, don't do anything
[ -z "$PS1" ] && return
# execution returns after this line
Now, for every alias in your bashrc file say i have:
alias ll="ls -l"
alias cls="clear;ls"
Create a file named after that alias say for ll:
user#host$ vi ssh_aliases/ll
#inside ll,write
ls -l
user#host$ chmod a+x ll
Now edit .bashrc to include:
# If not running interactively, don't do anything
[ -z "$PS1" ] && export $PATH=$PATH:~/ssh_aliases
This does the job.. although I am not sure if it is the best way to do so
EDIT(2)
You only need to do this for aliases, other commands in bashrc will be executed as pointed out by David "you must have executable for ssh to run commands".
an alternative to alias that will be visible in all script is
EXPORT & EXECUTE VARIABLE
# shortcut to set enviroment to insensitive case
export go_I="shopt -s nocasematch"
Now in any script you can use
#!/bin/bash
$go_I # go Insensitive
[[ a == A ]] # evaluates TRUE ( $? == 0)
$go_C # maibe want to go back to casesensitive
it's useful to place all shortcuts/aliases in /path/to/my_commands and edit /etc/bash.bashrc
source /path/to/my_commands
Open file ~/.bash_profile. If this file does not exist create one in the home directory and add the below line
source = $HOME/.bashrc
exit your ssh and login agian and you should get the .bashrc settings working for you.

Resources