How to use fish aliases in bash scripts - bash

I have a bash script and I want to use a command in it, that I defined in my config.fish like this:
alias setbg='feh --bg-fill'
However when I use the command in my bash script, I get:
setbg: command not found
How do I make fish aliases visible to bash scripts?

You don't.
Fish aliases are internal to fish only, and since bash and fish are incompatible it's not guaranteed you can source them either.
However, fish doesn't actually have aliases, the alias command is just a wrapper around defining functions, and unlike bash fish always reads its configuration.
So you can run fish -c 'setbg'.
Alternatively you could make a file with aliases that works in both bash and fish. As long as you stick to the common subset like simple alias key='value' that would work, but you couldn't use any incompatible expressions like even if.

Related

Script runs when executed but fails when sourced

Original Title: Indirect parameter substitution breaks when the script is sourced (zsh)
zsh 5.7.1 (x86_64-apple-darwin19.0)
GNU bash, version 4.4.20(1)-release (x86_64-pc-linux-gnu)
I’m developing a shell script on a Mac and I’m trying to keep it portable between bash & zsh, so array indexing is a consideration. I know that I can set KSH_ARRAYS to get indexing to start at 0, but I decided to query the OS for the shell that’s in use and set the start index accordingly, which led to the issue described below.
It made sense (to me anyway!) to use indirect expansion, which is what led to the problem. Consider the script indirect.sh:
#! /bin/bash
declare -r ARRAY_START_BASH=0
declare -r ARRAY_START_ZSH=1
declare -r SHELL_BASH=0
declare -r SHELL_ZSH=1
# Indirect expansion is used to reference the values of the variables declared
# in this case statement e.g. ${!ARRAY_START}
case $(basename $SHELL) in
"bash" )
declare -r SHELL_ID=SHELL_BASH
declare -r ARRAY_START=ARRAY_START_BASH
;;
"zsh" )
declare -r SHELL_ID=SHELL_ZSH
declare -r ARRAY_START=ARRAY_START_ZSH
;;
* )
return 1
;;
esac
echo "Shell ID: ${!SHELL_ID} Index arrays from: ${!ARRAY_START}"
It works fine when run from the command line while in the same directory:
<my home> ~ % echo "$(./indirect.sh)"
Shell ID: 1 Index arrays from: 1
Problems arise when I source the script:
<my home> ~ % echo "$(. ~/indirect.sh)"
/Users/<me>/indirect.sh:28: bad substitution
I don’t understand why sourcing the script changes the behavior of the parameter expansion.
Is this expected behavior? If so, I’d be grateful if someone could explain it and hopefully, offer a work around.
The problem described in the original post has nothing to do with indirect expansion. The difference in behavior is a result of different shells being invoked depending on whether the script is “executed” or “sourced”. These differences reveal the basic flaw in deriving the shell from the $SHELL variable that underpins the script's design. If the shell defined in $SHELL does not match the shebang, the script will fail either when sourced or executed. An explanation follows.
Indirect expansion doesn’t offer value in the given scenario because values could just as easily be assigned directly. They’ll have to be assigned that way regardless given the different syntax used for indirect expansion between shells. In fact, other syntax differences between shells makes the entire premise for detecting the shell moot! However, putting that aside, the difference in behavior is a result of different shells being invoked based on whether the script is “executed” or “sourced”. The behavior of sourcing is well documented with numerous explanations on the web, but for context here’s how it works:
Executing a Script
Use the “./“ syntax to execute a script.
When run this way, the script executes in a sub-shell. Any changes the
script makes to it’s shell are applied to the sub-shell, not the shell
in which the script was launched, so those changes are lost when the
shell exits because the sub-shell in which it executed is destroyed as
well. For example, if the script changes the working directory, it
does so in the sub-shell. The working directory of the main shell that
launched the script is unchanged when the script terminates. If you
want to make changes to the shell in which the script was launched, it
must be sourced.
Sourcing a Script
Use the “source “ syntax to source a
script. When run this way, the script essentially becomes an argument
for the source command, which handles invoking the appropriate
execution. Some shells (e.g. ksh) use a single period “.” instead of
“source”.
When a script is executed with the “./“ syntax, the shebang at the top of the file is used to determine which shell to use. When a script is sourced, the shebang is ignored and the shell in which the script is launched is used instead. Also note that the period that appears in the “./“ command syntax used to execute a script, is not related to the period that’s occasionally used as an alias for the source command.
The script in the post uses bash in the shebang statement, so it works when executed because it’s run using bash. When it’s sourced from zsh, it encounters the incorrect indirect expansion syntax:
“${!A_VAR}"
The correct syntax is:
"${(P)A_VAR}"
However, correcting the syntax won’t help because it will then fail when executed. The shebang will invoke bash and the syntax will be wrong again. That renders indirection useless for accessing a variable designed to indicate the shell in use. More importantly, a design based on querying an environment variable for the shell is flawed due to differences in the shell that’s ultimately used depending on whether the script is executed or sourced.
To add to your answer (what I'm going to say is too long for a comment), I can not think of any application, why your script could be useful if not sourced. Actually, I came accross the need of such a script by myself in exactly one occasion:
Since I use as interactive shell not only zsh, but also sometimes bash, so I have written my .zshrc and .bashrc to set up everything (including defining variables and shell functions for interactive use). In order to safe work,
I try to put code which works under both bash and zsh into a single file (say: .commonrc), and my .zshrc and .bashrc have inside them a
source .commonrc
While many things are so different in bash and zsh, that I can't put them into .commonrc, some can, provided I do some tweaking. One reason for headache is obviously the different indexing of arrays, which you seemingly try to solve. So I have also a similar feature. However, I don't nee ca case construct for this. Instead, my .bashrc looks like this (using your naming of the variables):
...
declare -r ARRAY_START=0
source .commonrc
...
and my .zshrc looks like this:
...
declare -r ARRAY_START=1
source .commonrc
...
Since it does not happen that the .bashrc is run from a zsh and vice versa, I don't need to query what kind of shell I have.

Create shell alias with semi-colon character

I've noticed that I have a tendency to mistype ls as ;s, so I decided that I should just create an alias so that instead of throwing an error, it just runs the command I mean.
However, knowing that the semi-colon character has a meaning in shell scripts/commands, is there any way to allow me to create an alias with that the semi-colon key? I've tried the following to no avail:
alias ;s=ls
alias ";s"=ls
alias \;=ls
Is it possible to use the semi-colon as a character in a shell alias? And how do I do so in ZSH?
First and foremost: Consider solving your problem differently - fighting the shell grammar this way is asking for trouble.
As far as I can tell, while you can define such a command - albeit not with an alias - the only way to call it is quoted, e.g. as \;s - which defeats the purpose; read on for technical details.
An alias won't work: while zsh allows you to define it (which, arguably, it shouldn't), the very mechanism that would be required to call it - quoting - is also the very mechanism that bypasses aliases and thus prevents invocation.
You can, however, define a function (zsh only) or a script in your $PATH (works in zsh as well as in bash, ksh, and dash), as long as you invoke it quoted (e.g., as \;s or ';s' or ";s"), which defeats the purpose.
For the record, here are the command definitions, but, again, they can only be invoked quoted.
Function (works in zsh only; place in an initialization file such as ~/.zshrc):
';s'() { ls "$#" }
Executable script ;s (works in dash, bash, ksh and zsh; place in a directory in your $PATH):
#!/bin/sh
ls "$#"

'which' command is incorrect

I have a shell script in my home directory called "echo". I added my home directory to my path, so that this echo would replace the other one.
To do this, I used: export PATH=/home/me:$PATH
When I do which echo, it shows the one I want. /home/me/echo
But when I actually do something like echo asdf it uses the system echo.
Am I doing something wrong?
which is an external command, so it doesn't have access to your current shell's built-in commands, functions, or aliases. In fact, at least on my system, /usr/bin/which is a shell script, so you can examine it and see how it works.
If you want to know how your shell will interpret a command, use type rather than which. If you're using bash, type -a will print all possible meanings in order of precedence. Consult your shell's documentation for details.
For most shells, built-in commands take precedence over commands in your $PATH. The whole point of having a built-in echo, for example, is that it's faster than loading /bin/echo into memory.
If you want your own echo command to override the shell's built-in echo, you can define it as a shell function.
On the other hand, overriding the built-in echo command doesn't strike me as a good idea in the first place. If it behaves the same as the built-in echo, there's not much point. If it doesn't, then it could break scripts that use echo expecting it to work a certain way. If possible, I suggest giving your command a different way. If it's an enhanced version of echo, you could even call it Echo.
It is likely using the shell's builtin.
If you want the one in your path you can do
`which echo` asdf
From this little article that explains the rules, here's a list in descending order of precedence:
Aliases
Shell functions
Shell builtin commands
Hash tables
PATH variable
echo is a shell builtin command (al least in bash) and PATH has the lowest priority. I guess you'll need to create a function or an alias.

Source a script with a different shell type

Let's say a script is called with /bin/sh. Is it possible to source another script from that script and to have it be interpreted with #!/bin/bash?
It would appear that the #!/bin/bash doesn't do anything...
And by source, at this point I am meaning the functionality of manipulating the parent environment.
No. The whole point of sourcing a script is that the script is interpreted by the shell doing the sourcing. If, as is often the case, /bin/sh is bash, then you will get the desired behavior. Otherwise, you are out of luck.
Try the source command, or dot operator. You might also try the env command. Note, make sure you export if you're using source (or dot).

Using aliases with nohup

Why doesn't the following work?
$ alias sayHello='/bin/echo "Hello world!"'
$ sayHello
Hello world!
$ nohup sayHello
nohup: appending output to `nohup.out'
nohup: cannot run command `sayHello': No such file or directory
(the reason I ask this question is because I've aliased my perl and python to different perl/python binaries which were optimized for my own purposes; however, nohup gives me troubles if I don't supply full path to my perl/python binaries)
Because the shell doesn't pass aliases on to child processes (except when you use $() or ``).
$ alias sayHello='/bin/echo "Hello world!"'
Now an alias is known in this shell process, which is fine but only works in this one shell process.
$ sayHello
Hello world!
Since you said "sayHello" in the same shell it worked.
$ nohup sayHello
Here, a program "nohup" is being started as a child process. Therefore, it will not receive the aliases.
Then it starts the child process "sayHello" - which isn't found.
For your specific problem, it's best to make the new "perl" and "python" look like the normal ones as much as possible. I'd suggest to set the search path.
In your ~/.bash_profile add
export PATH="/my/shiny/interpreters/bin:${PATH}"
Then re-login.
Since this is an environment variable, it will be passed to all the child processes, be they shells or not - it should now work very often.
For bash: Try doing nohup 'your_alias'. It works for me. I don't know why back quote is not shown. Put your alias within back quotes.
With bash, you can invoke a subshell interactively using the -i option. This will source your .bashrc as well as enable the expand_aliases shell option. Granted, this will only work if your alias is defined in your .bashrc which is the convention.
Bash manpage:
If the -i option is present, the shell is interactive.
expand_aliases: If set, aliases are expanded as described above under ALIASES. This option is enabled by default for interactive shells.
When an interactive shell that is not a login shell is started, bash reads and executes commands from /etc/bash.bashrc and ~/.bashrc, if these files exist.
$ nohup bash -ci 'sayHello'
If you look at the Aliases section of the Bash manual, it says
The first word of each simple command, if unquoted, is checked to see
if it has an alias.
Unfortunately, it doesn't seem like bash has anything like zsh's global aliases, which are expanded in any position.

Resources