What does `[[ -e ~/.profile ]] ` mean in zsh shell? - shell

I wanted to make zsh load ~/.profile at login. And I found this zsh-not-hitting-profile
Gilles's answers adding emulate sh -c '. ~/.profile' in ~/.zprofle
does work.
But I wonder why Frank Terbeck make an addition to it:
[[ -e ~/.profile ]] && emulate sh -c 'source ~/.profile'
I am not very familiar with linux shell so I don't understand what he says:
And it's only active during the source. So you do not have to save the current option state in order to replay it again after sourcing.
only active during the source? I need ~/.profile always need to be source , I can't get the meaning, because emulate sh -c 'source ~/.profile simply works everytime I logined.
save the current option state in order to replay? what is the option state, why I need to replay?

[[ -e ~/.profile ]] simply tests whether the file ~/.profile exists. This way you won't get an error doing source ~/.profile if the file isn't there.
"only active during the source" means that the -c option that you give to the emulate command doesn't change the options to the original shell process. It just uses that temporarily during the emulation of the source command.

Related

Bash completion does not work in ZSH/Oh-My-ZSH because COMP_WORDS is not an array

I'm writing a bash command line tool for which I want to enable bash completion using completely.
I have the following bash completion. After I eval "$(./cli completion)" (which outputs the below), completions work fine in bash:
#!/usr/bin/env bash
# This bash completions script was generated by
# completely (https://github.com/dannyben/completely)
# Modifying it manually is not recommended
_cli_completions() {
local cur=${COMP_WORDS[COMP_CWORD]}
local comp_line="${COMP_WORDS[*]:1}"
case "$comp_line" in
'completions'*) COMPREPLY=($(compgen -W "--help -h" -- "$cur")) ;;
'download'*) COMPREPLY=($(compgen -W "--force --help -f -h" -- "$cur")) ;;
''*) COMPREPLY=($(compgen -W "--help --version -h -v completions download" -- "$cur")) ;;
esac
}
complete -F _cli_completions cli
However, they do not work right in ZSH. I have identified the problem to be that COMP_WORDS is not an array when I'm inside ZSH, but it is inside bash. This then breaks the line local comp_line="${COMP_WORDS[*]:1}".
E.g. in the situation ./cli download <tab>, comp_line should be download, but in ZSH it's /cli download (only the . is removed), so I always end up in the last case ''*.
I'm using ZSH 5.8 with Oh-My-ZSH. Bash completions generally seem to work in ZSH.
Oh-My-ZSH sets up completions using
fpath=($ZSH/functions $ZSH/completions $fpath)
autoload -U compaudit compinit
I have tried setting setopt shwordsplit, hoping that that would cause COMP_WORDS to be treated as an array, but it had no effect.
Is there a ZSH configuration that I can change to make this completion script work?
Or is there a bash compatible change to the completion script that would make it work in both shells?
This issue is discussed in this Github ticket.
ZSH should be totally capable of handling bash completions when it is configured to do so by adding these two lines in ~/.zshrc:
# Load bash completion functions
autoload -Uz +X compinit && compinit
autoload -Uz +X bashcompinit && bashcompinit
In addition, the script in question, generated by the completely gem, seems to have a problem.
Replacing * with # in the below line seems to make it work on both bash and zsh.
local comp_line="${COMP_WORDS[*]:1}" # broken
local comp_line="${COMP_WORDS[#]:1}" # works

Can nano detect file type without extension by the shebang to have proper syntax highlighting?

I see nano cannot detect a file type by a shebang (hashbang) line like
#!/usr/bin/env bash
or similar.
Vim copes with this task w/o problems.
Is there a way to make it work for nano?
P.S. Created github issue.
P.P.S. Even nano 4.2 version doesn't support this. (compiled from sources on CentOS7)
There is a bug in nano syntax highlighting detection for .sh files, which I have found present in nano 4.8 and NOT present in nano 2.9.8, whereby a #! line with /env in it will not detect any shell except sh.
I have even found the specific commit which fixed it: https://git.savannah.gnu.org/cgit/nano.git/commit/?id=6a3ba2ab501c138c7ee1e72d2a65cea77342a43c
Annoyingly, at time of writing this is affecting .sh color syntax highlighting in up-to-date nano from the package manager on up-to-date current LTS version of Ubuntu (20.04).
To fix it, you have to replace your /usr/share/nano/sh.nanorc with the same file from a newer (or older!) version of nano.
The current one from https://git.savannah.gnu.org/cgit/nano.git/tree/syntax/sh.nanorc works fine.
I've decided to make simple wrapper for that.
#!/usr/bin/env bash
####################################################
# Find file type and set syntax highlight for nano #
####################################################
set -o pipefail
set -o errexit
set -o nounset
#set -o xtrace
# Determine path to nano binary file
if [[ -f /usr/local/bin/nano ]]; then
nano_bin=/usr/local/bin/nano
elif [[ -f /usr/bin/nano ]]; then
nano_bin=/usr/bin/nano
else
echo 'error: Sorry, nano binary file not found neither by path /usr/local/bin/nano nor /usr/bin/nano.' > /dev/stderr
exit 2
fi
# check if syntax highlight argument already passed
if ! echo ${#} | grep -E '(-Y|--syntax)' > /dev/null; then
# fetch interpreter name
syntax_type=$(head -1 bin/cli | grep '#!' | awk '{match($0,"([a-z]+)$",a)}END{print a[0]}')
if [[ -n "${syntax_type}" ]]; then
# map a file interpreter onto syntax type like BASH into SH
case "${syntax_type}" in
bash)
syntax_type=sh
;;
esac
nano_argument="--syntax=${syntax_type}"
fi
fi
${nano_bin} ${nano_argument:-} ${#}
Installation
Simple option for bash
Copy the code into ~/.nano-wrap.sh
nano ~/.nano-wrap.sh
Add an alias into your .bashrc file:
echo 'alias nano="bash ~/.nano-wrap.sh" >> ~/.bashrc'
And reload it:
source ~/.bashrc
J
I've found that saving the file (Ctrl+O, type name, Enter) will cause nano to auto-detect the file type from the shebang and then syntax-highlight the file appropriately from then on.

Makefile autocompletion on Mac

Makefile's targets are available by completion on Linux but, AFAICS, not on Mac OS (10.8.5).
Is it possible to get completion working with this OS?
This seems to achieve simple bash completions for me on El Capitan:
# .bashrc
function _makefile_targets {
local curr_arg;
local targets;
# Find makefile targets available in the current directory
targets=''
if [[ -e "$(pwd)/Makefile" ]]; then
targets=$( \
grep -oE '^[a-zA-Z0-9_-]+:' Makefile \
| sed 's/://' \
| tr '\n' ' ' \
)
fi
# Filter targets based on user input to the bash completion
curr_arg=${COMP_WORDS[COMP_CWORD]}
COMPREPLY=( $(compgen -W "${targets[#]}" -- $curr_arg ) );
}
complete -F _makefile_targets make
Here's how this works:
complete -F [function name] [command name] -- this bash builtin register a new completion for [command name] which is generated by the bash function [function name]. So in my code above, if you type make [TAB][TAB] into your shell, you'll trigger the _makefile_targets() function.
if [[ -e "$(pwd)/Makefile" ]]; then -- make sure there's a Makefile in the current directory, otherwise don't try a bash completion.
grep -oE '^[a-zA-Z0-9_-]+:' Makefile -- filter every line of Makefile using the regex for a target name like "test:". -o means only return the part of the line that matches. For example, given a Makefile target like "test: build package", only "test:" will be returned
| sed 's/://' -- taking the grep results, remove the colon from the end of line
| tr '\n' ' ' -- smoosh all targets onto one line, separated by one space
Inside a bash completion function, complete sets several environment variables for you. COMP_WORDS is an array of the list of available bash completion choises based on what the user typed. COMP_CWORD is the index of the currently selected word.
Another very magical builtin compgen will take a list of space separately strings and filter them using the currently selected word. I'm not at all clear how that works.
So, the bottom line is that the last two lines in the function filter our list of makefile targets (stored inside $targets) and shoves them into an array COMPREPLY. The bash completion reads and displays COMPREPLY as choices in the shell.
Inspired by:
https://gist.github.com/tlrobinson/1073865
http://www.thegeekstuff.com/2013/12/bash-completion-complete/ (Esp 9.)
This worked for me on Catalina in the standard zsh terminal
Edit the file named .zshrc in you home directory this can be done with this command in the terminal nano ~/.zshrc
Add the following lines
zstyle ':completion:*:*:make:*' tag-order 'targets'
autoload -U compinit && compinit
exit and save by pressing ctrl + x and then save Y
For macOS Monterey, the following works:
Edit the .zprofile file in your home directory. This can be done with a text editor. Path: ~/.zprofile
Add the following lines to the end of the file:
zstyle ':completion:*:*:make:*' tag-order 'targets'
autoload -Uz compinit && compinit
After that, everything works correctly, tips work when you press TAB.
This method does not display anything extra when entering the terminal, unlike the methods indicated above.
If you use bash and homebrew:
brew install bash-completion
follow their documentation:
Add the following line to your ~/.bash_profile:
[[ -r "$(brew --prefix)/etc/profile.d/bash_completion.sh" ]] && . "$(brew --prefix)/etc/profile.d/bash_completion.sh"
restart your terminal or
source ~/.bash_profile

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").

How to find out where alias (in the bash sense) is defined when running Terminal in Mac OS X

How can I find out where an alias is defined on my system? I am referring to the kind of alias that is used within a Terminal session launched from Mac OS X (10.6.3).
For example, if I enter the alias command with no parameters at a Terminal command prompt, I get a list of aliases that I have set, for example:
alias mysql='/usr/local/mysql/bin/mysql'
However, I have searched all over my system using Spotlight and mdfind in various startup files and so far can not find where this alias has been defined. ( I did it a long time ago and didn't write down where I assigned the alias).
For OSX, this 2-step sequence worked well for me, in locating an alias I'd created long ago and couldn't locate in expected place (~/.zshrc).
cweekly:~ $ which la
la: aliased to ls -lAh
cweekly:~$ grep -r ' ls -lAh' ~
/Users/cweekly//.oh-my-zsh/lib/aliases.zsh:alias la='ls -lAh'
Aha! "Hiding" in ~/.oh-my-zsh/lib/aliases.zsh. I had poked around a bit in .oh-my-zsh but had overlooked lib/aliases.zsh.
you can just simply type in alias on the command prompt to see what aliases you have. Otherwise, you can do a find on the most common places where aliases are defined, eg
grep -RHi "alias" /etc /root
First use the following commands
List all functions
functions
List all aliases
alias
If you aren't finding the alias or function consider a more aggressive searching method
Bash version
bash -ixlc : 2>&1 | grep thingToSearchHere
Zsh version
zsh -ixc : 2>&1 | grep thingToSearchHere
Brief Explanation of Options
-i Force shell to be interactive.
-c Take the first argument as a command to execute
-x -- equivalent to --xtrace
-l Make bash act as if invoked as a login shell
Also in future these are the standard bash config files
/etc/profile
~/.bash_profile or ~/.bash_login or ~/.profile
~/.bash_logout
~/.bashrc
More info: http://www.heimhardt.com/htdocs/bashrcs.html
A bit late to the party, but I was having the same problem (trying to find where the "l." command was aliased in RHEL6), and ended up in a place not mentioned in the previous answers. It may not be found in all bash implementations, but if the /etc/profile.d/ directory exists, try grepping there for unexplained aliases. That's where I found:
[user#server ~]$ grep l\\. /etc/profile.d/*
/etc/profile.d/colorls.csh:alias l. 'ls -d .*'
/etc/profile.d/colorls.csh:alias l. 'ls -d .* --color=auto'
/etc/profile.d/colorls.sh: alias l.='ls -d .*' 2>/dev/null
/etc/profile.d/colorls.sh:alias l.='ls -d .* --color=auto' 2>/dev/null
The directory isn't mentioned in the bash manpage, and isn't properly part of where bash searches for profile/startup info, but in the case of RHEL you can see the calling code within /etc/profile:
for i in /etc/profile.d/*.sh ; do
if [ -r "$i" ]; then
if [ "${-#*i}" != "$-" ]; then
. "$i"
else
. "$i" >/dev/null 2>&1
fi
fi
done
Please do check custom installations/addons/plugins you have added, in addition to the .zshrc/.bashrc/.profile etc files
So for me: it was git aliased to 'g'.
$ which g
g: aliased to git
Then I ran the following command to list all aliases
$ alias
I found a whole lot of git related aliases that I knew I had not manually added.
This got me thinking about packages or configurations I had installed. And so went to the
.oh-my-zsh
directory. Here I ran the following command:
$ grep -r 'git' . |grep -i alias
And lo and behold, I found my alias in :
./plugins/git/git.plugin.zsh
I found the answer ( I had been staring at the correct file but missed the obvious ).
The aliases in my case are defined in the file ~/.bash_profile
Somehow this eluded me.
For more complex setups (e.g. when you're using a shell script framework like bash-it, oh-my-zsh or the likes) it's often useful to add 'alias mysql' at key positions in your scripts. This will help you figure out exactly when the alias is added.
e.g.:
echo "before sourcing .bash-it:"
alias mysql
. $HOME/.bash-it/bash-it.sh
echo "after sourcing bash:"
alias mysql
I think that maybe this is similar to what ghostdog74 meant however their command didn't work for me.
I would try something like this:
for i in `find . -type f`; do # find all files in/under current dir
echo "========"
echo $i # print file name
cat $i | grep "alias" # find if it has alias and if it does print the line containing it
done
If you wanted to be really fancy you could even add an if [[ grep -c "alias" ]] then <print file name>
The only reliable way of finding where the alias could have been defined is by analyzing the list of files opened by bash using dtruss.
If
$ csrutil status
System Integrity Protection status: enabled.
you won't be able to open bash and you may need a copy.
$ cp /bin/bash mybash
$ $ codesign --remove-signature mybash
and then use
sudo dtruss -t open ./mybash -ic exit 2>&1 | awk -F'"' '/^open/ {print substr($2, 0, length($2)-2)}'
to list all the files where the alias could have been defined, like
/dev/dtracehelper
/dev/tty
/usr/share/locale/en_CA.UTF-8/LC_MESSAGES/BASH.mo
/usr/share/locale/en_CA.utf8/LC_MESSAGES/BASH.mo
/usr/share/locale/en_CA/LC_MESSAGES/BASH.mo
/usr/share/locale/en.UTF-8/LC_MESSAGES/BASH.mo
/usr/share/locale/en.utf8/LC_MESSAGES/BASH.mo
/usr/share/locale/en/LC_MESSAGES/BASH.mo
/Users/user/.bashrc
/Users/user/.bash_aliases
/Users/user/.bash_history
...
Try: alias | grep name_of_alias
Ex.: alias | grep mysql
or, as already mentioned above
which name_of_alias
In my case, I use Oh My Zsh, so I put aliases definition in ~/.zshrc file.

Resources