bash auto complete binary file which is not in PATH - linux

I'm trying to make a custom bash auto completion script on my CLI package.
When I install my package like below, then my command is installed in $PATH (/usr/local/bin),
$ ./configure
$ make
$ sudo make install
so complete -o filenames -F _mycommand mycommand in my bash-autocomplete.sh works properly.
(Because command mycommand is in $PATH (/usr/local/bin)
However, when I install my package locally, and then try to execute binary file from installed location like below,
$ ./configure --prefix=$HOME/usr
$ make
$ make install
complete -o filenames -F _mycommand mycommand doesn't work because OS don't know the location of mycommand.
$ ~/uftrace$ $HOME/usr/bin/mycommand [TAB]
Command 'command' not found,
My question is this:
How can I make bash completion feature with my local binary file? (which is not in PATH)
Can I do this by fixing Makefile or configure or bash-autocomplete.sh?
+
Install the package locally, and than add PATH is not an option because I want to make this bash auto-completion feature regardless of installation location. I want to this feature work at installation point.

From the documentation, that's not possible unless using an "intelligent" completion loader:
First, the command name is identified. [...]
If the command word is a full pathname, a compspec for the full pathname is
searched for first. If no compspec is found for the full pathname, an
attempt is made to find a compspec for the portion following the final
slash.
I put an emphasis (or bold) on the part that should apply: bash will be able to complete the full path (eg: /usr/mycommand or even ./mycommand) but it won't be able to resolve mycommand unless it is found in the PATH and where some completion does the trick.
At last resort, you could register a completion loader which may for example look at the command (using ${1##*/} to get the basename):
_completion_loader() {
if [[ "${1}" == mycommand || "${1##*/}" == mycommand ]]; then
complete -o filenames -F _mycommand "$1"
return 124
fi
return 1 #
}
complete -D -F _completion_loader -o bashdefault -o default
I would not do that on Linux, due to chance of bash-completion begin already there and providing completion by itself: you could check bash-completion as they may perhaps have a way to handle your completion, for example by saving in /etc/bash_completion.d/<yourcommand>.bash.

Related

(oh-my-)zsh behaviour: `$ command_name` at home directory results in `cd command_name` effect

I'm currently using zsh with oh-my-zsh, and I've run into an annoying shell behaviour.
I must have done a subtle yet breaking change in the $PATH export while editing my .zshrc, because the following thing happens:
At the shell prompt while in ~/, issuing, for example, the flutter command...
$ flutter
...results in:
$ ~/flutter/
(as if calling $ flutter had been interpreted as $ cd flutter)
However, issuing $ flutter in any other directory, including $ ~/flutter results in the correct execution of the flutter command. Same thing for go and others.
Line 2 on my .zshrc exports $PATH in the following fashion:
export PATH=$HOME/bin:$HOME/.emacs.d:$HOME/flutter/bin/cache/dart-sdk:$HOME/flutter/bin/cache/dart-sdk/bin:$HOME/.pub-cache/bin:$HOME/.composer/vendor/bin:$HOME/.cargo/env:$HOME/.platformio/penv/bin:$HOME/flutter/bin:$PATH
I've been comparing .zshrc versions with other backups, and I may be missing something, but no differences were found.
What seems off, in your opinion?
If you've encountered this annoyance before, how did you correct it?
Explanation
It'a feature called AUTO_CD.
AUTO_CD (-J)
If a command is issued that can’t be executed as a normal command, and the command is the name of a directory, perform the cd command to that directory.
http://zsh.sourceforge.net/Doc/Release/Options.html#Changing-Directories
AUTO_CD is enabled by oh-my-zsh in file oh-my-zsh/lib/theme-and-appearance.zsh.
...
setopt auto_cd
setopt multios
setopt prompt_subst
[[ -n "$WINDOW" ]] && SCREEN_NO="%B$WINDOW%b " || SCREEN_NO=""
...
Solution
Append the following command in ~/.zshrc after oh-my-zsh is loaded to disable this feature.
unsetopt AUTO_CD

Aliasing a command and using it in the same line of code

I was wondering if you could alias a command, and use it in the same line of code, see this example:
alias php=/opt/plesk/php/5.6/bin/php; php -v;
I want this to output PHP 5.6.
alias php=/opt/plesk/php/7.3/bin/php; php -v;
and I want this to output PHP 7.3. However, what I get is this:
php -v
# outputs 5.6
alias php=/opt/plesk/php/5.6/bin/php; php -v;
# outputs 5.6
alias php=/opt/plesk/php/7.3/bin/php; php -v;
# outputs 5.6
php -v
# outputs 7.3
I've tried the && operator but it has the same outcome.
I'm wanting to use this in a gitlab continuous integration script, which executes a script through ssh -t by passing a string. However I am calling several php functions and I dont want to paste the full php path every time:
ssh -v -tt $SSH_HOST_NAME "__my_php_commands_here__"
I think the command line is being parsed, and aliases applied, before anything is executed. However, you can do it with shell functions. I don't have PHP, but I have several Perl versions to test with:
$ perl -v |grep version # V
This is perl 5, version 26, subversion 2 (v5.26.2) built for x86_64-cygwin-threads-multi
$ perl(){ /usr/bin/perl "$#" ; } ; perl -v |grep version
This is perl 5, version 26, subversion 3 (v5.26.3) built for x86_64-cygwin-threads-multi
# ^
So defining the pass-through function
perl(){ /usr/bin/perl "$#" ; }
changes how the word perl is interpreted later in the command line. Note that you do need the ; before } — see this answer.
For your use case, I would recommend using a different name to avoid confusion. E.g.:
currphp(){ /opt/plesk/php/5.6/bin/php "$#" ; } ; currphp -v
currphp(){ /opt/plesk/php/7.3/bin/php "$#" ; } ; currphp -v
in a gitlab continuous integration script
Batch non-interactive shells don't support aliases (by default). And that's a good think. alias should be just used as your own, custom shorthand, not in batch scrips.
You could
a) define a function with the same name and use full path for command resolution:
php() { /opt/plesk/php/5.6/bin/php "$#"; }
php -v
Downsides: function are not exported, unless you add export -f php, it is a shell function. Something like xargs php will work incorrectly.
b) use a varaible.
php=/opt/plesk/php/5.6/bin/php
"$php" -v
Downside: you have to modify all scripts and always check out for the $php.
c) Modify the path, so your php is found first. You could create a temporary directory and add it to path:
tmpd=$(mktemp -d)
trap 'rm -r "$tmpd"' EXIT
ln -s /opt/plesk/php/5.6/bin/php "$tmpd"/php
export PATH="$tmpd"/php:$PATH
php -v
If you export PATH correctly, it will work everywhere. Remember to remove the folder tho.
Side note: You don't "alias a variable", you "alias a command". alias allows you to substitute the first (and only the first) word in a simple command. php is a command.
I've went with a .bashrc solution. I initially started with this but for some reason the aliases werent being picked up. It appears you need to set a custom expand_aliases setting.
My .bashrc ended up looking like this:
shopt -s expand_aliases;
alias php=/opt/plesk/php/7.3/bin/php;
alias composer=/usr/lib64/plesk-9.0/composer.phar;
This seemed to do the trick and gave me the correct PHP version while using ssh xx#1.2.3.4 "php -v".

Executing bash cd command in Makefile [duplicate]

For example, I have something like this in my makefile:
all:
cd some_directory
But when I typed make I saw only 'cd some_directory', like in the echo command.
It is actually executing the command, changing the directory to some_directory, however, this is performed in a sub-process shell, and affects neither make nor the shell you're working from.
If you're looking to perform more tasks within some_directory, you need to add a semi-colon and append the other commands as well. Note that you cannot use new lines as they are interpreted by make as the end of the rule, so any new lines you use for clarity need to be escaped by a backslash.
For example:
all:
cd some_dir; echo "I'm in some_dir"; \
gcc -Wall -o myTest myTest.c
Note also that the semicolon is necessary between every command even though you add a backslash and a newline. This is due to the fact that the entire string is parsed as a single line by the shell. As noted in the comments, you should use '&&' to join commands, which means they only get executed if the preceding command was successful.
all:
cd some_dir && echo "I'm in some_dir" && \
gcc -Wall -o myTest myTest.c
This is especially crucial when doing destructive work, such as clean-up, as you'll otherwise destroy the wrong stuff, should the cd fail for whatever reason.
A common usage, though, is to call make in the subdirectory, which you might want to look into. There's a command-line option for this, so you don't have to call cd yourself, so your rule would look like this
all:
$(MAKE) -C some_dir all
which will change into some_dir and execute the Makefile in that directory, with the target "all". As a best practice, use $(MAKE) instead of calling make directly, as it'll take care to call the right make instance (if you, for example, use a special make version for your build environment), as well as provide slightly different behavior when running using certain switches, such as -t.
For the record, make always echos the command it executes (unless explicitly suppressed), even if it has no output, which is what you're seeing.
Starting from GNU make 3.82 (July 2010), you can use the .ONESHELL special target to run all recipes in a single instantiation of the shell (bold emphasis mine):
New special target: .ONESHELL instructs make to invoke a single instance of the shell and provide it with the entire recipe, regardless of how many lines it contains.
.ONESHELL: # Applies to every targets in the file!
all:
cd ~/some_dir
pwd # Prints ~/some_dir if cd succeeded
another_rule:
cd ~/some_dir
pwd # Prints ~/some_dir if cd succeeded
Note that this will be equivalent to manually running
$(SHELL) $(.SHELLFLAGS) "cd ~/some_dir; pwd"
# Which gets replaced to this, most of the time:
/bin/sh -c "cd ~/some_dir; pwd"
Commands are not linked with && so if you want to stop at the first one that fails, you should also add the -e flag to your .SHELLFLAGS:
.SHELLFLAGS += -e
Also the -o pipefail flag might be of interest:
If set, the return value of a pipeline is the value of the last (rightmost) command to exit with a non-zero status, or zero if all commands in the pipeline exit successfully. This option is disabled by default.
Here's a cute trick to deal with directories and make. Instead of using multiline strings, or "cd ;" on each command, define a simple chdir function as so:
CHDIR_SHELL := $(SHELL)
define chdir
$(eval _D=$(firstword $(1) $(#D)))
$(info $(MAKE): cd $(_D)) $(eval SHELL = cd $(_D); $(CHDIR_SHELL))
endef
Then all you have to do is call it in your rule as so:
all:
$(call chdir,some_dir)
echo "I'm now always in some_dir"
gcc -Wall -o myTest myTest.c
You can even do the following:
some_dir/myTest:
$(call chdir)
echo "I'm now always in some_dir"
gcc -Wall -o myTest myTest.c
What do you want it to do once it gets there? Each command is executed in a subshell, so the subshell changes directory, but the end result is that the next command is still in the current directory.
With GNU make, you can do something like:
BIN=/bin
foo:
$(shell cd $(BIN); ls)
Here is the pattern I've used:
.PHONY: test_py_utils
PY_UTILS_DIR = py_utils
test_py_utils:
cd $(PY_UTILS_DIR) && black .
cd $(PY_UTILS_DIR) && isort .
cd $(PY_UTILS_DIR) && mypy .
cd $(PY_UTILS_DIR) && pytest -sl .
cd $(PY_UTILS_DIR) && flake8 .
My motivations for this pattern are:
The above solution is simple and readable (albeit verbose)
I read the classic paper "Recursive Make Considered Harmful", which discouraged me from using $(MAKE) -C some_dir all
I didn't want to use just one line of code (punctuated by semicolons or &&) because it is less readable, and I fear that I will make a typo when editing the make recipe.
I didn't want to use the .ONESHELL special target because:
that is a global option that affects all recipes in the makefile
using .ONESHELL causes all lines of the recipe to be executed even if one of the earlier lines has failed with a nonzero exit status. Workarounds like calling set -e are possible, but such workarounds would have to be implemented for every recipe in the makefile.
To change dir
foo:
$(MAKE) -C mydir
multi:
$(MAKE) -C / -C my-custom-dir ## Equivalent to /my-custom-dir
PYTHON = python3
test:
cd src/mainscripts; ${PYTHON} -m pytest
#to keep make file in root directory and run test from source root above #worked for me.
Like this:
target:
$(shell cd ....); \
# ... commands execution in this directory
# ... no need to go back (using "cd -" or so)
# ... next target will be automatically in prev dir
Good luck!

Bash commands don't work after changing Path variable

I'm unable to use ls, bash.. any popular commands that are critical after changing the path.
I'm unsure what it was before (because I can't do vi command either).
I ran the first command, and realized the first one had a typo - not PATH, but I've typed PATh.
So I immediately ran the next one:
export PATH="/usr/local/bin:$PATh"
export PATH="/usr/local/bin:$PATH"
Then vi, ls, bash commands started to not work.
I did echo $PATH to see the PATH.
usr/local/bin:/usr/local/bin:/usr/local/bin:/usr/local/bin:
This is what I got. Any help is appreciated.
You should be able to source /etc/profile to reset your PATH variable, though it may step on a few other variables you've configured along the way. You could also just grep for the appropriate line from that to set PATH and redo that in your current environment
Also, you can always specify the full path to an executable you need in the interim. For example, if you wanted to use grep with the PATH in its current state you could use /bin/grep (or perhaps /usr/bin/grep depending your system)
1 > Try to load default .profile script
$ /bin/bash ./.profile
2 > Just Logout from current session
and Re-login it.
It appears you have "broken" your bash shell ~/.bash_profile script, because of the PATh typo. (The question explicitly states bash, so I'm answering in that context.)
Since the PATH is "broken", you will need to access your editor by using its fully qualified path.
/usr/bin/vi ~/.bash_profile
That should allow you to fix the PATh to be PATH.
If you find that you need to edit your PATH environment variable a lot, this little bash function can be helpful.
vipath() {
local tmpfile=$(mktemp /tmp/vipath-edit.XXXXXX)
echo "$PATH" | tr ':' '\n' > "$tmpfile"
vi "$tmpfile" && PATH=$(paste -s -d ':' "$tmpfile")
rm "$tmpfile"
}
Note: there are better ways to ensure the $tmpfile gets deleted that I did not use in this snippet. And on a multiuser system, there are better ways to make sure the temporary file is not located in a shared location.
If you want to add a directory location to your PATH, without adding duplicate locations, this little bash function can be helpful to prepend a directory location.
pathadd() {
if [ -d "$1" ] && [[ ! ":$PATH:" =~ ":$1:" ]]
then
PATH="$1:$PATH"
fi
}
I had the same in RHEL8, I did an export PATH for a certain directory and then no command worked anymore. I performed a faulty export PATH command probably.
I got messages like this:
>$ yum
bash: yum: command not found...
Install package 'yum' to provide command 'yum'? [N/y] n
Luckily I had some other similar systems where I could get the path from, so I did:
export PATH=/home/USER/.local/bin:/home/USER/bin:/usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbine
Change USER to your own.
To make it permanent: add to $HOME/.bashrc with:
export PATH=$PATH:<YOUR PATH HERE>
When you do export PATH="/usr/local/bin:$PATH" you are saying, set PATH to /usr/local/bin: plus whatever was in the PATH variable before. This essentially concatenates the string "/usr/local/bin:" with the old path. That is why your PATH repeats so much, you must have run that command a few times.
Try running this: export PATH="/usr/local/bin".

/bin/sh: pushd: not found

I am doing the following inside a make file
pushd %dir_name%
and i get the following error
/bin/sh : pushd : not found
Can someone please tell me why this error is showing up ?
I checked my $PATH variable and it contains /bin so I don't think that is causing a problem.
pushd is a bash enhancement to the POSIX-specified Bourne Shell. pushd cannot be easily implemented as a command, because the current working directory is a feature of a process that cannot be changed by child processes. (A hypothetical pushd command might do the chdir(2) call and then start a new shell, but ... it wouldn't be very usable.) pushd is a shell builtin, just like cd.
So, either change your script to start with #!/bin/bash or store the current working directory in a variable, do your work, then change back. Depends if you want a shell script that works on very reduced systems (say, a Debian build server) or if you're fine always requiring bash.
add
SHELL := /bin/bash
at the top of your makefile
I have found it on another question How can I use Bash syntax in Makefile targets?
A workaround for this would be to have a variable get the current working directory. Then you can cd out of it to do whatever, then when you need it, you can cd back in.
i.e.
oldpath=`pwd`
#do whatever your script does
...
...
...
# go back to the dir you wanted to pushd
cd $oldpath
sudo dpkg-reconfigure dash
Then select no.
This is because pushd is a builtin function in bash. So it is not related to the PATH variable and also it is not supported by /bin/sh (which is used by default by make. You can change that by setting SHELL (although it will not work directly (test1)).
You can instead run all the commands through bash -c "...". That will make the commands, including pushd/popd, run in a bash environment (test2).
SHELL = /bin/bash
test1:
#echo before
#pwd
#pushd /tmp
#echo in /tmp
#pwd
#popd
#echo after
#pwd
test2:
#/bin/bash -c "echo before;\
pwd; \
pushd /tmp; \
echo in /tmp; \
pwd; \
popd; \
echo after; \
pwd;"
When running make test1 and make test2 it gives the following:
prompt>make test1
before
/download/2011/03_mar
make: pushd: Command not found
make: *** [test1] Error 127
prompt>make test2
before
/download/2011/03_mar
/tmp /download/2011/03_mar
in /tmp
/tmp
/download/2011/03_mar
after
/download/2011/03_mar
prompt>
For test1, even though bash is used as a shell, each command/line in the rule is run by itself, so the pushd command is run in a different shell than the popd.
This ought to do the trick:
( cd dirname ; pwd ); pwd
The parentheses start a new child shell, thus the cd changes the directory within the child only, and any command after it within the parentheses will run in that folder. Once you exit the parentheses you are back in wherever you were before..
here is a method to point
sh -> bash
run this command on terminal
sudo dpkg-reconfigure dash
After this you should see
ls -l /bin/sh
point to /bin/bash (and not to /bin/dash)
Reference
Your shell (/bin/sh) is trying to find 'pushd'. But it can't find it because 'pushd','popd' and other commands like that are build in bash.
Launch you script using Bash (/bin/bash) instead of Sh like you are doing now, and it will work
Synthesizing from the other responses: pushd is bash-specific and you are make is using another POSIX shell. There is a simple workaround to use separate shell for the part that needs different directory, so just try changing it to:
test -z gen || mkdir -p gen \
&& ( cd $(CURRENT_DIRECTORY)/genscript > /dev/null \
&& perl genmakefile.pl \
&& mv Makefile ../gen/ ) \
&& echo "" > $(CURRENT_DIRECTORY)/gen/SvcGenLog
(I substituted the long path with a variable expansion. I probably is one in the makefile and it clearly expands to the current directory).
Since you are running it from make, I would probably replace the test with a make rule, too. Just
gen/SvcGenLog :
mkdir -p gen
cd genscript > /dev/null \
&& perl genmakefile.pl \
&& mv Makefile ../gen/ \
echo "" > gen/SvcGenLog
(dropped the current directory prefix; you were using relative path at some points anyway)
And than just make the rule depend on gen/SvcGenLog. It would be a bit more readable and you can make it depend on the genscript/genmakefile.pl too, so the Makefile in gen will be regenerated if you modify the script. Of course if anything else affects the content of the Makefile, you can make the rule depend on that too.
Note that each line executed by a make file is run in its own shell anyway. If you change directory, it won't affect subsequent lines. So you probably have little use for pushd and popd, your problem is more the opposite, that of getting the directory to stay changed for as long as you need it!
Run "apt install bash"
It will install everything you need and the command will work

Resources