bashrc not exporting variable in function when changing directories - bash

I have several projects I work on. Instead of setting aliases for each project's location, I would rather set it when I navigate to that specific directory. Each project is a git repo and I already have a mechanism that appends the current branch name to $PS1 when I navigate to it.
In my .bashrc, I call a function: parse_git_branch to append to command prompt:
case "$TERM" in
xterm*|rxvt*)
PS1="\[\e]0;${debian_chroot:+($debian_chroot)}\u#\h:\w\a\]$PS1\$(parse_git_branch) "
;;
*)
;;
esac
parse_git_branch is defined at the end of my .basrhc:
function parse_git_branch {
GIT_BRANCH=$(git branch --no-color 2> /dev/null | sed -e '/^[^*]/d' -e 's/* \(.*\)/ \[\1\]/')
if [[ -n $GIT_BRANCH ]];then
source $HOME/bin/cur
fi
echo $GIT_BRANCH
}
if GIT_BRANCH isn't empty, I source a simple script ~/bin/cur which sets an alias for the pwd:
#!/bin/bash
echo "got here!"
shopt -s expand_aliases
alias current="cd $(pwd)"
When cur is sourced, the alias is not set. The debug message does display correctly however.
I believe this is because shell scripts run "outside" of my current environment. The alias is set when call source cur from the command line.
So, why is the alias not being set when called from "parse_git_branch" inside my .bashrc?
Thanks!

Problem here is usage of $() that runs parse_git_branch in a subshell. As a result, alias current is not found in opened terminal because aliases are not visible by parent processes.
case "$TERM" in
xterm*|rxvt*)
PS1="\[\e]0;${debian_chroot:+($debian_chroot)}\u#\h:\w\a\]$PS1\$(parse_git_branch) "
source $HOME/bin/cur
;;
*)
;;
should work.

Ok, I figured it out. The issue here isn't why I'm trying to do this. The issue lays in the fact that I'm trying to affect a parent shell environment from a subshell environment spawned from it (The $() command syntax bart spoke of.)
So the workaround was to allow communication from both environments. For that, I simply used the filesystem. Here's what I did:
.bashrc didn't have to change much, but it didn't have to source anything either:
parse_git_branch {
GIT_BRANCH=$(git branch --no-color 2> /dev/null | sed -e '/^[^*]/d' -e 's/* \(.*\)/ \[\1\]/')
if [[ -n $GIT_BRANCH ]];then
$HOME/bin/cur -p
fi
echo $GIT_BRANCH
}
I modified ~/bin/cur to simply write or read the fs, not attempt to modify any environments:
#!/bin/bash
put() {
echo $(pwd) > $HOME/.tmpalias
}
get() {
cat $HOME/.tmpalias
}
case "$1" in
*p)
put
;;
*g)
get
;;
esac
The cur script will either write or read from the file .tmpalias when called (I didn't bother with arguments and files etc since this is only called in two places.
Then I added the following to my .bash_aliases file:
alias current='cd $(cur -g)'
Voila! So I can expand this into an event trapping mechanism that can be invoked for any command I run.

Related

Using anyenv in Fish Shell

I tried to using anyenv in Fish Shell but the error occured.
This is my ~./config/fish/config.fish file.
set -x PATH $HOME/.anyenv/bin $PATH
eval (anyenv init -)
What's wrong with this?
Errors which I got:
$# is not supported. In fish, please use 'count $argv'.
- (line 1): begin; source "/Users/kasumi/.anyenv/libexec/../completions/anyenv.bash" anyenv() { typeset command command="$1" if [ "$#" -gt 0 ]; then shift fi command anyenv "$command" "$#" } export NDENV_ROOT="/Users/kasumi/.anyenv/envs/ndenv" export PATH="/Users/kasumi/.anyenv/envs/ndenv/bin:$PATH" export PATH="/Users/kasumi/.anyenv/envs/ndenv/shims:${PATH}" source "/Users/kasumi/.anyenv/envs/ndenv/libexec/../completions/ndenv.bash" ndenv rehash 2>/dev/null ndenv() { typeset command command="$1" if [ "$#" -gt 0 ]; then shift fi case "$command" in rehash|shell) eval "`ndenv "sh-$command" "$#"`";; *) command ndenv "$command" "$#";; esac }
^
from sourcing file -
called on line 60 of file /usr/local/Cellar/fish/2.4.0/share/fish/functions/eval.fish
in function 'eval'
called on line 6 of file ~/.config/fish/config.fish
from sourcing file ~/.config/fish/config.fish
called on standard input
source: Error while reading file '-'
Added:
I tried answer (https://stackoverflow.com/a/42119354/7524270) but I get some new errors.
New Errors:
Variables cannot be bracketed. In fish, please use "$PATH".
- (line 1): begin; source "/Users/kasumi/.anyenv/libexec/../completions/anyenv.fish" function anyenv set command $argv[1] set -e argv[1] command anyenv "$command" $argv end set -x NDENV_ROOT "/Users/kasumi/.anyenv/envs/ndenv" set -x PATH $PATH "/Users/kasumi/.anyenv/envs/ndenv/bin" export PATH="/Users/kasumi/.anyenv/envs/ndenv/shims:${PATH}" ndenv rehash 2>/dev/null ndenv() { typeset command command="$1" if [ "$#" -gt 0 ]; then shift fi case "$command" in rehash|shell) eval "`ndenv "sh-$command" "$#"`";; *) command ndenv "$command" "$#";; esac }
^
from sourcing file -
called on line 60 of file /usr/local/Cellar/fish/2.5.0/share/fish/functions/eval.fish
in function 'eval'
called on line 4 of file ~/.config/fish/config.fish
from sourcing file ~/.config/fish/config.fish
called during startup
source: Error while reading file '-'
Quick fix:
In ~/.config/fish/config.fish, change:
eval (anyenv init -)
to:
eval (anyenv init - fish)
EDIT: or a slightly more bullet-proof approach:
eval (command anyenv init - fish)
(command forces fish to ignore functions)
What's going on?
anyenv init uses the $SHELL environment variable to determine your active shell and returns some commands, to be executed by the shell, in order to finish the initialization of anyenv.
For some reason the $SHELL variable in your environment points to bash and not to fish (you can verify this by running echo $SHELL).
The most probable reason is that fish is simply not the default shell for your user. You may execute grep $USER /etc/passwd to find out (look at what it says after the last :). It's possible to change the default shell with chsh, but if you decide to do it, do it with great care. There might be some important settings in your .bashrc or some other programs may depend on a POSIX-compliant shell to work properly.
Another option is that somewhere you have an override of the $SHELL variable, such that it points to bash and not fish. I have this in my byobu/tmux configuration because it helped me avoid some strange behavior.
Luckily, anyenv init lets the user (you) specify the shell manually (ignore $SHELL), and that should fix your issue with anyenv.

Create shell sub commands by hierarchy

I'm trying to create a system for my scripts -
Each script will be located in a folder, which is the command itself.
The script itself will act as a sub-command.
For example, a script called "who" inside a directory called "git",
will allow me to run the script using git who in the command line.
Also, I would like to create a sub command to a psuedo-command, meaning a command not currently available. E.g. some-arbitrary-command sub-command.
Is that somehow possible?
I thought of somehow extending https://github.com/basecamp/sub to accomplish the task.
EDIT 1
#!/usr/bin/env bash
command=`basename $0`
subcommand="$1"
case "$subcommand" in
"" | "-h" | "--help" )
echo "$command: Some description here" >&2
;;
* )
subcommand_path="$(command -v "$command-$subcommand" || true)"
if [[ -x "$subcommand_path" ]]; then
shift
exec "$subcommand_path" "${#}"
return $?
else
echo "$command: no such command \`$subcommand'" >&2
exit 1
fi
;;
esac
This is currently the script I run for new custom-made commands.
Since it's so generic, I just copy-paste it.
I still wonder though -
can it be generic enough to just recognize the folder name and create the script by its folder name?
One issue though is that it doesn't seem to override the default command name, if it supposed to replace it (E.g. git).
EDIT 2
After tinkering around a bit this is what I came to eventuall:
#!/usr/bin/env bash
COMMAND=`basename $0`
SUBCOMMAND="$1"
COMMAND_DIR="$HOME/.zsh/scripts/$COMMAND"
case "$SUBCOMMAND" in
"" | "-h" | "--help" )
cat "$COMMAND_DIR/help.txt" 2>/dev/null ||
command $COMMAND "${#}"
;;
* )
SUBCOMMAND_path="$(command -v "$COMMAND-$SUBCOMMAND" || true)"
if [[ -x "$SUBCOMMAND_path" ]]; then
shift
exec "$SUBCOMMAND_path" "${#}"
else
command $COMMAND "${#}"
fi
;;
esac
This is a generic script called "helper-sub" I symlink to all the script directories I have (E.g. ln -s $HOME/bin/helper-sub $HOME/bin/ssh).
in my zshrc I created this to call all the scripts:
#!/usr/bin/env bash
PATH=${PATH}:$(find $HOME/.zsh/scripts -type d | tr '\n' ':' | sed 's/:$//')
export PATH
typeset -U path
for aliasPath in `find $HOME/.zsh/scripts -type d`; do
aliasName=`echo $aliasPath | awk -F/ '{print $NF}'`
alias ${aliasName}=${aliasPath}/${aliasName}
done
unset aliasPath
Examples can be seen here: https://github.com/iwfmp/zsh/tree/master/scripts
You can't make a directory executable as a script, but you can create a wrapper that calls the scripts in the directory.
You can do this either with a function (in your profile script or a file in your FPATH) or with a wrapper script.
A simple function might look like:
git() {
local subPath='/path/to/your/git'
local sub="${1}" ; shift
if [[ -x "${subPath}/${1}" ]]; then
"${subPath}/${sub}" "${#}"
return $?
else
printf '%s\n' "git: Unknown sub-command '${sub}'." >&2
return 1
fi
}
(This is the same way that the sub project you linked works, just simplified.)
Of course, if you actually want to create a sub-command for git specifically (and that wasn't just an example), you'll need to make sure that the built-in git commands still work. In that case you could do like this:
git() {
local subPath='/path/to/your/git'
local sub="${1}"
if [[ -x "${subPath}/${sub}" ]]; then
shift
"${subPath}/${sub}" "${#}"
return $?
else
command git "${#}"
return 1
fi
}
But it might be worth pointing out in that case that git supports adding arbitrary aliases via git config:
git config --global alias.who '!/path/to/your/git/who'

Bash function - return parent script file path

I have a bash script containing a function which is sourced by a number of different bash scripts. This function may fail based on its input, and I'd like to create logging within the function to identify what script(s) are causing failures.
E.g.,
source /path/to/function.sh
The closest I've come is this:
ps --no-heading -ocmd -p $$
This works well enough if the full file path is used to run the parent script, returning:
/bin/bash /path/to/parent.sh
But it fails to provide the full path if the parent script is run from a relative path, returning:
/bin/bash ./parent.sh
Ideally, I'd like a way to reliably return the parent script file path for both cases.
I suppose I could have each parent script pass its file path to the function (via $0 or similar), but that seems hard to enforce and not terribly elegant.
Any ideas, or alternative approaches? Should I not worry about the relative path case, and just use full/absolute file paths for everything?
Thanks!
I'm using Centos 5.9.
Bash version -
GNU bash, version 3.2.25(1)-release (x86_64-redhat-linux-gnu)
You can use readlink to follow all symbolic links to get an absolute path.
echo $(readlink -f $0)
As soon as the parent script starts export
"`pwd`/$0"
or so, into an env variable, say ORIG_SCRIPT, then in the function just use ORIG_SCRIPT.
You need to do this as soon as the script starts because $0 may be relative to the PWD and if you later change PWD before you need the value of ORIG_SCRIPT, it gets unnecessarily complicated.
Update:
Since you know the pid by $$, you may get something from /proc/<PID>/cmdline but I don't know how exactly this one works right now.
You could use ${BASH_SOURCE[1]} to get the script that calls the function but that is not always on absolute path form. You could get the absolute path of it by readlink -m, realpath, or other shell-script based solutions, but if your script changes directory from time to time, conversion of relative paths to absolute paths would no longer be accurate as those tools base from the current directory to get the actual form.
There's a workaround however but this requires that you won't change directories in your scripts before calling (sourcing) the script that contains the function. You would have to save the current directory in that script itself then base forming of absolute paths through that directory. You are free to change directories after the script has already been included. As an example:
ORIGINAL_PWD=$PWD
function x {
local CALLING_SCRIPT="${BASH_SOURCE[1]}"
if [[ -n $CALLING_SCRIPT ]]; then
if [[ $CALLING_SCRIPT == /* ]]; then
CALLING_SCRIPT=$(readlink -m "$CALLING_SCRIPT")
else
CALLING_SCRIPT=$(readlink -m "$ORIGINAL_PWD/$CALLING_SCRIPT")
fi
echo "Calling script: $CALLING_SCRIPT"
else
echo "Caller is not a script."
fi
}
Or
ORIGINAL_PWD=$PWD
function getabspath {
local -a T1 T2
local -i I=0
local IFS=/ A
case "$1" in
/*)
read -r -a T1 <<< "$1"
;;
*)
read -r -a T1 <<< "/$PWD/$1"
;;
esac
T2=()
for A in "${T1[#]}"; do
case "$A" in
..)
[[ I -ne 0 ]] && unset T2\[--I\]
continue
;;
.|'')
continue
;;
esac
T2[I++]=$A
done
case "$1" in
*/)
[[ I -ne 0 ]] && __="/${T2[*]}/" || __=/
;;
*)
[[ I -ne 0 ]] && __="/${T2[*]}" || __=/.
;;
esac
}
function x {
local CALLING_SCRIPT="${BASH_SOURCE[1]}"
if [[ -n $CALLING_SCRIPT ]]; then
if [[ $CALLING_SCRIPT == /* ]]; then
getabspath "$CALLING_SCRIPT"
else
getabspath "$ORIGINAL_PWD/$CALLING_SCRIPT"
fi
echo "Calling script: $__"
else
echo "Caller is not a script."
fi
}
You could also play around with FUNCNAME and BASH_LINENO to be more specific with the errors. I'm just not sure if they're already supported in Bash 3.2.
If you actually had Bash 4.0+ you could make use of associative arrays to map absolute paths with it but if there are two scripts with the same names or are called with almost similar names, one value could be overridden. There's no fix to that since we can't choose our keys from BASH_SOURCE.
Added Note: You could also prevent your script from being unnecessarily sourced multiple times as it only requires to be once through a solution like Shell Script Loader. You might find convenience through it as well.

Is there a way to have a multiline shebang for a Groovy script?

The following almost works:
#!/bin/bash
/* 2>&1 >/dev/null
script_dir=$(dirname "$0")
export GROOVY_HOME=${script_dir}/../../../../Tools/groovy/groovy-2.0.2
exec ${GROOVY_HOME}/bin/groovy -cp "${script_dir}:$(ls ${script_dir}/build/lib/runtime/*.jar | xargs echo | sed -e 's| |:|g')" "$0"
*/ // 2>&1 >/dev/null
println("aoeu")
The only problem is that the shell globs /* and tries to execute it. In the end, all I really want to do is to be able to build the Groovy script's classpath without having to have two separate scripts.
You can also try the technique below, which is independent of Groovy syntax (and does not normally produce output on stderr):
#!/bin/sh
script_dir=$(dirname "$0")
export GROOVY_HOME="$script_dir/../../../../Tools/groovy/groovy-2.0.2"
awk 'mark_on{print}/^__END__$/{mark_on=1}' "$0" >/tmp/$$.groovy
"$GROOVY_HOME/bin/groovy" -cp "$script_dir:$(echo "$script_dir"/build/lib/runtime/*.jar | tr " " :)" /tmp/$$.groovy
status=$?
rm -f /tmp/$$.groovy
exit $status
__END__
println("aoeu")
Also notice the simplification in the classpath calculation; remember that globbing (wildcard expansion) is performed by the shell, not by the command that takes the arguments, so you do not have to (nor do you want to) use ls in this case.
The curly brackets ${} in your original code are technically superfluous in this case (they are purely stylistic); they would be needed if you had for example to append a string directly after a variable substitution where there is no clear break between the variable name and what follows, e.g. you cannot say $my_varsome_string but you can write ${my_var}some_string, or any of $my_var"some_string" or $my_var'some_string' or "$my_var"some_string or "$my_var""some_string". I removed the braces for "minimalistic" purposes and in order to illustrate the above, but again, it's perfectly fine to keep them for stylistic reasons.
The quotes I added consistently in the code above protect you from potential blanks and certain other special characters inside $GROOVY_HOME. Feel free to remove them in order to simplify quoting (and be minimalistic) if you know $GROOVY_HOME will not contain blanks.
It seems you want to write a groovy / shell polyglot.
I don't know groovy, but from the documentation it seems [] is a valid groovy command, creating an empty list.
Then you could write it as follow:
#!/bin/bash
[ /* 2> /dev/null > /dev/null
script_dir=$(dirname "$0")
export GROOVY_HOME=${script_dir}/../../../../Tools/groovy/groovy-2.0.2
exec ${GROOVY_HOME}/bin/groovy -cp "${script_dir}:$(ls ${script_dir}/build/lib/runtime/*.jar | xargs echo | sed -e 's| |:|g')" "$0"
*/
]
println("aoeu")
Groovy will read [] and ignore it, and bash will call [ with /*, which will cause an error that is ignored. But it will not run any unexpected programs.
#!/bin/bash
script_dir="$(cd $(dirname $0) >/dev/null; pwd -P)"
function after-bangshe() {
sed -e '1,/^!#$/d' "$1"
}
if [ -z "${GROOVY_HOME}" ]
then
echo 'GROOVY_HOME must be defined.' >&2
exit 1
fi
CLASSPATH="${script_dir}" "${GROOVY_HOME}/bin/groovy" -e "$(after-bangshe $0)" "$#"
exit
!#
println 'aoeu'
The output of cd is redirected to /dev/null in case CDPATH is set (which makes cd noisy).
CLASSPATH is set to the script directory so that any support classes can be found.
The sed command strips out everything after the !# line.

exiting script while running source scriptname over SSH

I have a script with a number of options in it one of the option sets is supposed to change the directory and then exit the script however running over ssh with the source to get it to change in the parent it exits SSH is there another way to do this so that it does not exit? my script is in the /usr/sbin directory.
You might try having the script run a subshell instead of whatever method it is using to “change [the directory] in the parent” (presumably you have the child print out a cd command and have the parent do something like eval "$(script --print-cd)"). So instead of (e.g.) a --print-cd option, add a --subshell option that starts a new instance of $SHELL.
d=/path/to/some/dir
#...
cd "$d"
#...
if test -n "$opt_print_cd"; then
sq_d="$(printf %s "$d" | sed -e "s/'/'\\\\''/g")"
printf "cd '%s'\n" "$sq_d"
elif test -n "$opt_subshell"; then
exec "$SHELL"
fi
If you can not edit the script itself, you can make a wrapper (assuming you have permission to create new, persistent files on the ‘server’):
#!/bin/sh
script='/path/to/script'
print_cd=
for a; do test "$a" = --print-cd && print_cd=yes && break; done
if test -n "$print_cd"; then
eval "$("$script" ${1+"$#"})" # use cd instead of eval if the script prints a bare dir path
exec "$SHELL"
else
exec $script" ${1+"$#"}
fi

Resources