Subshell `(alias; declare -f)` in a function - bash

I defined the following which function as recommended in man which:
The recommended way to use this utility is by adding an alias (C shell)
or shell function (Bourne shell) like the following:
which()
{
(alias; declare -f) | /usr/bin/which --tty-only --read-alias --read-functions --show-tilde --show-dot $#
}
export -f which
Unlike /usr/bin/which which only finds commands, this function finds commands, aliases and functions. My question is why is (alias; declare -f) being piped into /usr/bin/which $#?

/usr/bin/which is not built into the shell. Consequently, it has no way to access shell-internal state (like aliases or function definitions) unless that content is fed into it.
That's what's being done here.
However, that's completely unnecessary on any modern shell. Feeding shell syntax info an external program to be parsed there is innately unreliable compared to having the shell itself give you the output you need.
Don't do this. Use the shell built-in type instead.

Related

Export a function from zsh to bash script

I have a function defined in my .zshenv like this:
function my_func() {
echo $1
}
I then execute a bash script from the zsh which its content is:
type my_func
I got an error: /tmp/test.bash: line 3: type: my_func: not found
While if I type type my_func from the zsh I got: my_func is a shell function from
Is there a way to use zsh defined function in a bash script? It seems to work for the exported variables
How bash does it
Bash itself can export its functions to other bash shells. It does so by exporting a string environmental variable of the form:
BASH_FUNC_functionNameHere%%=() { functionBodyHere; }
So in theory you could use the following zsh-command to export your function for bash:
export "BASH_FUNC_my_func%%=() { $(echo; typeset -f my_func | tail -n+2) }"
However, this does not work because zsh doesn't allow %% in identifiers.
Workaround
Depending on how you start bash, you might be able to inject the function definition into the bash process.
When running a bash script like bash myScript or ./myScript you can use bash -c "$(typeset -f my_func); export -f my_func; myScript" instead.
When starting an interactive bash shell using bash, you can use bash -c "$(typeset -f my_func); export -f my_func; exec bash" instead.
The right way
Either way, your function has to be a polyglot. That is, the source code of the function has to be understood by both zsh and bash. Above approach is not really viable if you want to export many functions or want to call many bash scripts.
It would be easier to define each of your functions inside its own script file and add the locations of those scripts to $PATH. That way you can call your "functions" from every shell and they will always work independently from your current shell.
Replacing the functions by script files only works if your functions don't want to modify the parent shell. cd or setting variables has no effect on the caller. If you want to do stuff like this, you can still use a script file, but then have to source it using . myFunctionFile. For sourcing, the source code has to be a polyglot again.

Bash what bash alias actually is? [duplicate]

I'm surprised hasn't been asked before, but…
What is the difference between
alias ⇢ alias EXPORT='alias'
function ⇢ function exporter() { echo $EXPORT }
and
export ⇢ export ALIAS='export'
and for that matter...
alias export=$(function) (j/k)
in bash (zsh, et al.)
Specifically, I'd be most interested in knowing the lexical/practical difference between
alias this=that
and
export that=this
I have both forms... all over the place - and would prefer to stop arbitrarily choosing one, over the other. 😂
I'm sure there is a great reference to a "scopes and use-cases for unix shells", somewhere... but thought I'd post the question here, in the name of righteous-canonicalicism.
You're asking about two very different categories of things: aliases and functions define things that act like commands; export marks a variable to be exported to child processes. Let me go through the command-like things first:
An alias (alias ll='ls -l') defines a shorthand for a command. They're intended for interactive use (they're actually disabled by default in shell scripts), and are simple but inflexible. For example, any arguments you specify after the alias simply get tacked onto the end of the command; if you wanted something like alias findservice='grep "$1" /etc/services', you can't do it, because $1 doesn't do anything useful here.
A function is like a more flexible, more powerful version of an alias. Functions can take & process arguments, contain loops, conditionals, here-documents, etc... Basically, anything you could do with a shell script can be done in a function. Note that the standard way to define a function doesn't actually use the keyword function, just parentheses after the name. For example: findservice() { grep "$1" /etc/services; }
Ok, now on to shell variables. Before I get to export, I need to talk about unexported variables. Basically, you can define a variable to have some (text) value, and then if you refer to the variable by $variablename it'll be substituted into the command. This differs from an alias or function in two ways: an alias or function can only occur as the first word in the command (e.g. ll filename will use the alias ll, but echo ll will not), and variables must be explicitly invoked with $ (echo $foo will use the variable foo, but echo foo will not). More fundamentally, aliases and functions are intended to contain executable code (commands, shell syntax, etc), while variables are intended to store non-executable data.
(BTW, you should almost always put variable references inside double-quotes -- that is, use echo "$foo" instead of just echo $foo. Without double-quotes the variable's contents get parsed in a somewhat weird way that tends to cause bugs.)
There are also some "special" shell variables, that are automatically set by the shell (e.g. $HOME), or influence how the shell behaves (e.g. $PATH controls where it looks for executable commands), or both.
An exported variable is available both in the current shell, and also passed to any subprocesses (subshells, other commands, whatever). For example, if I do LC_ALL=en_US.UTF-8, that tells my current shell use the "en_US.UTF-8" locale settings. On the other hand, if I did export LC_ALL=en_US.UTF-8 that would tell the current shell and all subprocesses and commands it executes to use that locale setting.
Note that a shell variable can be marked as exported separately from defining it, and once exported it stays exported. For example, $PATH is (as far as I know) always exported, so PATH=/foo:/bar has the same effect as export PATH=/foo:/bar (although the latter may be preferred just in case $PATH somehow wasn't already exported).
It's also possible to export a variable to a particular command without defining it in the current shell, by using the assignment as a prefix for the command. For example LC_ALL=en_US.UTF-8 sort filename will tell the sort command to use the "en_US.UTF-8" locale settings, but not apply that to the current shell (or any other commands).
TL;DR:
The shell evaluation order (per POSIX) for the entities in your question is:
aliases --> variables --> command substitutions --> special built-ins --> functions --> regular built-ins
Aliases do not persist across subshells, but variables (and in Bash, functions) can be made to do so with the export command.
Regular built-ins can be overridden by writing functions that have the same name as the regular built-in (since functions expand before regular built-ins). (NOTE: If you're trying to add functionality to the regular built-in, call the built-in with command in your function definition so you don't accidentally create a recursive function.)
Variables can be made readonly with the (special built-in) readonly command, but aliases cannot.
USE CASES:
Export a variable if you need to use a variable across subshells.
Make a variable readonly if you don't want it changed for the life of the parent shell (once performed, this cannot be undone with unset; you must restart the parent shell).
If you want to override or add functionality to a regular built-in, use a function.
NOTE: If you want to be sure that you're using a special or regular built-in and not someone else's function, use builtin the_builtin, or if the shell doesn't support the builtin command, use the POSIX comand command -p the_builtin, where the -p switch tells command to use the $PATH that ships with the shell by default (in case the user has overriden path).
NOTE: A variable can be made to act like an alias that also persists across subshells and cannot be changed. For example,
#! /bin/sh
my_cmd='ls -al'
export my_cmd
readonly my_cmd
will act like
#! /bin/sh
alias my_cmd='ls -al'
so long as
my_cmd is used without double-quotes (i.e. ${my_cmd}, NOT "${my_cmd}") so it isn't treated as a single string, and
IFS is the standard space-tab-newline and not switched to something else so that the elements of my_cmd are globbed and each part separated by a space is evaluated as a single token (otherwise it will be evaluated as a single string).
Each shell (e.g. bash, zsh, ksh, yash, etc.) is a bit different, so be sure to review the reference manual for it (they each implement POSIX in a unique way, or sometimes not at all).

Is function definition always exported in bash?

I an learning bash.
In bash manual, I found document about function that says
Functions may be exported so that subshells automatically
have them defined with the -f option to the export builtin.
In my bash, it exports definition of function contrary to the shell variable. But bash manual uses word, "may be exported". Is function definition always exported in bash or should I do something to guarantee its exportation?
Functions may be exported ... with the -f option to the export builtin.
That removes all the fluff. It should be clearer as to what it's trying to say.
And just in case it still isn't...
export -f <funcname>
Whether something (a variable or function) is exported or not determines whether it'll be passed on to subprocesses. For shell functions, this only really matters if the subprocess happens to be another shell. Here's an illustration:
$ exportedfunc() { echo "This is the exported function"; }
$ export -f exportedfunc
$ nonexportedfunc() { echo "This is the non-exported function"; }
$ bash # create a subshell to see which functions it inherits
$ PS1='\$\$ ' # set a different prompt so we can tell the subshell ($$) from the parent shell ($)
$$ exportedfunc # This'll work, because the parent shell exported the function
This is the exported function
$$ nonexportedfunc # This won't work because this function was not exported to subprocesses
bash: nonexportedfunc: command not found
$$ exit # back to the parent shell, where both functions are defined
$ exportedfunc
This is the exported function
$ nonexportedfunc
This is the non-exported function
I don't know of any shell setting that would cause all functions to be exported automatically. Although if you create a subshell implicitly (e.g. by putting some commands in parentheses), it'll inherit everything whether exported or not.

Bash variable space in directory path

How can I remove a white space when I use a variable in a directory path.
For example, I'm trying to do
alias test='echo /david/$1'
and when I try
test hhh
this produces
/david/ hhh
with a white space before the variable. It seems very simple but I can't find a solution.
alias doesn't do parameter expansion. At all. Use a function instead.
test (){
echo "/david/$1"
}
man bash:
There is no mechanism for using arguments in the replacement text. If arguments are needed, a shell function should be used (see FUNCTIONS below). [...] For almost every purpose, aliases are superseded by shell functions.
As a part of alias expansion a space is added at the end of the expanded string (otherwise no argument could be added, like alias ll='ls -l'. So ll -a would be expanded to ls -l-a which would be wrong). So I see no solution to this problem anything else then to use function as Ignacio proposed.
Anyway using test as function or alias is not the best thing to do as it is a bash built-in command (if no aliased or named a function). You can check how a mnemonic will be interpreted using the type bash built-in.
I defined an alias and a function called test:
$ type test
type test
test is aliased to `echo /xxx/'
$ type -a test
type -a test
test is aliased to `echo /xxx/'
test is a function
test ()
{
echo "/yyy/$1"
}
test is a shell builtin
test is /usr/bin/test

Exactly what does env do in Bash?

I get this behavior when using Bash (under Cygwin):
$ printf '\u00d5'
\u00d5
$ env printf '\u00d5' # This results in the behavior I want
Õ
It does not matter if I use UTF-8 or ISO-8859-1 encoding in the terminal.
My questions are: Exactly what does env do? Why do I need it in this specific case?
env is not in bash, it's a standalone executable, it is used to set or clear environment variable before running program. In your particular case, it's running the binary printf instead of the shell builtin. You can achieve the same result by using the absolute path:
/usr/bin/printf '\u00d5'
The least intrusive method is perhaps the following: redefine the printf function and have Bash handle the rest. Source a file that contains the following:
function printf()
{
$(which printf) "$#"
}
or as a one-liner function printf() { $(which printf) "$#"; }. Of course you could replace the $(which printf) for a /usr/bin/printf ...
Then simply use printf as you're used to. Your script stays the same and you could even introduce a condition to define the function only on certain Bash versions.
AFAIK you can also leave out the function, but I find it adds to readability.
[EDIT: the function keyword is a bash extension; printf () { ...; } is the POSIX syntax. If you do use the function keyword, the () after the function name are optional.]
Commonly, env is also used in the hash-bang lines of scripts that strive to be portable. The reason being that env is nearly always at /usr/bin/env, while bash isn't always at /bin/bash as many a hash-bang line implies. Example:
#!/usr/bin/env bash
also works for other programs/interpreters:
#!/usr/bin/env python

Resources