What is the purpose of a command that does nothing, being little more than a comment leader, but is actually a shell builtin in and of itself?
It's slower than inserting a comment into your scripts by about 40% per call, which probably varies greatly depending on the size of the comment. The only possible reasons I can see for it are these:
# poor man's delay function
for ((x=0;x<100000;++x)) ; do : ; done
# inserting comments into string of commands
command ; command ; : we need a comment in here for some reason ; command
# an alias for `true'
while : ; do command ; done
I guess what I'm really looking for is what historical application it might have had.
Historically, Bourne shells didn't have true and false as built-in commands. true was instead simply aliased to :, and false to something like let 0.
: is slightly better than true for portability to ancient Bourne-derived shells. As a simple example, consider having neither the ! pipeline operator nor the || list operator (as was the case for some ancient Bourne shells). This leaves the else clause of the if statement as the only means for branching based on exit status:
if command; then :; else ...; fi
Since if requires a non-empty then clause and comments don't count as non-empty, : serves as a no-op.
Nowadays (that is: in a modern context) you can usually use either : or true. Both are specified by POSIX, and some find true easier to read. However there is one interesting difference: : is a so-called POSIX special built-in, whereas true is a regular built-in.
Special built-ins are required to be built into the shell; Regular built-ins are only "typically" built in, but it isn't strictly guaranteed. There usually shouldn't be a regular program named : with the function of true in PATH of most systems.
Probably the most crucial difference is that with special built-ins, any variable set by the built-in - even in the environment during simple command evaluation - persists after the command completes, as demonstrated here using ksh93:
$ unset x; ( x=hi :; echo "$x" )
hi
$ ( x=hi true; echo "$x" )
$
Note that Zsh ignores this requirement, as does GNU Bash except when operating in POSIX compatibility mode, but all other major "POSIX sh derived" shells observe this including dash, ksh93, and mksh.
Another difference is that regular built-ins must be compatible with exec - demonstrated here using Bash:
$ ( exec : )
-bash: exec: :: not found
$ ( exec true )
$
POSIX also explicitly notes that : may be faster than true, though this is of course an implementation-specific detail.
I use it to easily enable/disable variable commands:
#!/bin/bash
if [[ "$VERBOSE" == "" || "$VERBOSE" == "0" ]]; then
vecho=":" # no "verbose echo"
else
vecho=echo # enable "verbose echo"
fi
$vecho "Verbose echo is ON"
Thus
$ ./vecho
$ VERBOSE=1 ./vecho
Verbose echo is ON
This makes for a clean script. This cannot be done with '#'.
Also,
: >afile
is one of the simplest ways to guarantee that 'afile' exists but is 0 length.
A useful application for : is if you're only interested in using parameter expansions for their side-effects rather than actually passing their result to a command.
In that case, you use the parameter expansion as an argument to either : or false depending upon whether you want an exit status of 0 or 1. An example might be
: "${var:=$1}"
Since : is a builtin, it should be pretty fast.
: can also be for block comment (similar to /* */ in C language). For example, if you want to skip a block of code in your script, you can do this:
: << 'SKIP'
your code block here
SKIP
Two more uses not mentioned in other answers:
Logging
Take this example script:
set -x
: Logging message here
example_command
The first line, set -x, makes the shell print out the command before running it. It's quite a useful construct. The downside is that the usual echo Log message type of statement now prints the message twice. The colon method gets round that. Note that you'll still have to escape special characters just like you would for echo.
Cron job titles
I've seen it being used in cron jobs, like this:
45 10 * * * : Backup for database ; /opt/backup.sh
This is a cron job that runs the script /opt/backup.sh every day at 10:45. The advantage of this technique is that it makes for better looking email subjects when the /opt/backup.sh prints some output.
It's similar to pass in Python.
One use would be to stub out a function until it gets written:
future_function () { :; }
If you'd like to truncate a file to zero bytes, useful for clearing logs, try this:
:> file.log
You could use it in conjunction with backticks (``) to execute a command without displaying its output, like this:
: `some_command`
Of course you could just do some_command > /dev/null, but the :-version is somewhat shorter.
That being said I wouldn't recommend actually doing that as it would just confuse people. It just came to mind as a possible use-case.
It's also useful for polyglot programs:
#!/usr/bin/env sh
':' //; exec "$(command -v node)" "$0" "$#"
~function(){ ... }
This is now both an executable shell-script and a JavaScript program: meaning ./filename.js, sh filename.js, and node filename.js all work.
(Definitely a little bit of a strange usage, but effective nonetheless.)
Some explication, as requested:
Shell-scripts are evaluated line-by-line; and the exec command, when run, terminates the shell and replaces it's process with the resultant command. This means that to the shell, the program looks like this:
#!/usr/bin/env sh
':' //; exec "$(command -v node)" "$0" "$#"
As long as no parameter expansion or aliasing is occurring in the word, any word in a shell-script can be wrapped in quotes without changing its' meaning; this means that ':' is equivalent to : (we've only wrapped it in quotes here to achieve the JavaScript semantics described below)
... and as described above, the first command on the first line is a no-op (it translates to : //, or if you prefer to quote the words, ':' '//'. Notice that the // carries no special meaning here, as it does in JavaScript; it's just a meaningless word that's being thrown away.)
Finally, the second command on the first line (after the semicolon), is the real meat of the program: it's the exec call which replaces the shell-script being invoked, with a Node.js process invoked to evaluate the rest of the script.
Meanwhile, the first line, in JavaScript, parses as a string-literal (':'), and then a comment, which is deleted; thus, to JavaScript, the program looks like this:
':'
~function(){ ... }
Since the string-literal is on a line by itself, it is a no-op statement, and is thus stripped from the program; that means that the entire line is removed, leaving only your program-code (in this example, the function(){ ... } body.)
Self-documenting functions
You can also use : to embed documentation in a function.
Assume you have a library script mylib.sh, providing a variety of functions. You could either source the library (. mylib.sh) and call the functions directly after that (lib_function1 arg1 arg2), or avoid cluttering your namespace and invoke the library with a function argument (mylib.sh lib_function1 arg1 arg2).
Wouldn't it be nice if you could also type mylib.sh --help and get a list of available functions and their usage, without having to manually maintain the function list in the help text?
#!/bin/bash
# all "public" functions must start with this prefix
LIB_PREFIX='lib_'
# "public" library functions
lib_function1() {
: This function does something complicated with two arguments.
:
: Parameters:
: ' arg1 - first argument ($1)'
: ' arg2 - second argument'
:
: Result:
: " it's complicated"
# actual function code starts here
}
lib_function2() {
: Function documentation
# function code here
}
# help function
--help() {
echo MyLib v0.0.1
echo
echo Usage: mylib.sh [function_name [args]]
echo
echo Available functions:
declare -f | sed -n -e '/^'$LIB_PREFIX'/,/^}$/{/\(^'$LIB_PREFIX'\)\|\(^[ \t]*:\)/{
s/^\('$LIB_PREFIX'.*\) ()/\n=== \1 ===/;s/^[ \t]*: \?['\''"]\?/ /;s/['\''"]\?;\?$//;p}}'
}
# main code
if [ "${BASH_SOURCE[0]}" = "${0}" ]; then
# the script was executed instead of sourced
# invoke requested function or display help
if [ "$(type -t - "$1" 2>/dev/null)" = function ]; then
"$#"
else
--help
fi
fi
A few comments about the code:
All "public" functions have the same prefix. Only these are meant to be invoked by the user, and to be listed in the help text.
The self-documenting feature relies on the previous point, and uses declare -f to enumerate all available functions, then filters them through sed to only display functions with the appropriate prefix.
It is a good idea to enclose the documentation in single quotes, to prevent undesired expansion and whitespace removal. You'll also need to be careful when using apostrophes/quotes in the text.
You could write code to internalize the library prefix, i.e. the user only has to type mylib.sh function1 and it gets translated internally to lib_function1. This is an exercise left to the reader.
The help function is named "--help". This is a convenient (i.e. lazy) approach that uses the library invoke mechanism to display the help itself, without having to code an extra check for $1. At the same time, it will clutter your namespace if you source the library. If you don't like that, you can either change the name to something like lib_help or actually check the args for --help in the main code and invoke the help function manually.
I saw this usage in a script and thought it was a good substitute for invoking basename within a script.
oldIFS=$IFS
IFS=/
for basetool in $0 ; do : ; done
IFS=$oldIFS
...
this is a replacement for the code: basetool=$(basename $0)
Another way, not yet mentioned here is the initialisation of parameters in infinite while-loops. Below is not the cleanest example, but it serves it's purpose.
#!/usr/bin/env bash
[ "$1" ] && foo=0 && bar="baz"
while : "${foo=2}" "${bar:=qux}"; do
echo "$foo"
(( foo == 3 )) && echo "$bar" && break
(( foo=foo+1 ))
done
Related
I am trying to write a simple Bash completion script for a program that runs its arguments as a command. A good example of this is kind of program is the prime-run script provided by the nvidia-prime package:
#!/bin/bash
__NV_PRIME_RENDER_OFFLOAD=1 __VK_LAYER_NV_optimus=NVIDIA_only __GLX_VENDOR_LIBRARY_NAME=nvidia "$#"
This script sets a few environment variables, which instructs the prime driver to use the Nvidia dGPU on a hybrid system. The first argument is treated as the command, and all trailing arguments are passed through. So for example you can run prime-run code . and VSCode will start in the current directory using the dGPU.
Therefore from a completion-script POV, what we want is to basically try to complete as if the prime-run token isn't there (hence "transparent proxy"-like behaviour). To give a rather contrived example:
> prime-run journalc<TAB>
(completes journalctl)
> prime-run journalctl --us<TAB>
(completes --user)
However I am finding this surprisingly difficult in Bash (not that I know how in other shells). So the question is simple: is it possible and if so how?
Ideas I've (hopelessly) had
The simple complete -A command prime-run: the first argument gets completed as a command as expected (let's call it foo), but the following arguments are also completed as commands rather than as arguments to foo
Use some combination of compgen and complete -p to invoke the completion function of foo, but AFAIK the completion function for all foo is locally defined and thus uncallable
TL;DR
bash-completion provides a function named _command_offset (permalink), which is exactly what I need.
# A meta-command completion function for commands like sudo(8), which need to
# first complete on a command, then complete according to that command's own
# completion definition.
Keep reading if you are interested in how I got here.
So I was daydreaming the other day, when it hit me - doesn't sudo basically have the exact same behaviour I want? So the task became simple - reverse engineer the completion script for sudo. Source available here: permalink.
Turns out, most of the code has to do with completing the various options, so it's safe to simply throw most of it out:
L 8-11, 50-52: Related to sudo's edit mode. Safe to ditch.
L 19-24, 27-39, 43-49: These complete sudo's options. Safe to ditch.
So we're left with this:
_sudo()
{
local cur prev words cword split
_init_completion -s || return
for ((i = 1; i <= cword; i++)); do
if [[ ${words[i]} != -* ]]; then
local PATH=$PATH:/sbin:/usr/sbin:/usr/local/sbin
local root_command=${words[i]}
_command_offset $i
return
fi
done
$split && return
} &&
complete -F _sudo sudo sudoedit
The for and if block are there to deal with sudo's options that precede the "guest command". Safe to ditch (after replacing all $i with 1).
The variable $split is only referenced in _init_completion (permalink), and it seems to be used for handling different argument styles (--foo=bar v.s. --foo bar). Same with the -s flag. Irrelevant.
Appending to $PATH and setting $root_command have to do with privilege escalation. Only relevant to sudo.
So after the dust has cleared, by process of elimination, I ended up with this simple chunk of code:
_my-script()
{
local cur prev words cword
_init_completion || return
_command_offset 1
} && complete -F _my-script my-script
Declaring these four local variables and calling _init_completion is standard for all completion scipts, so really it's as simple as one command. Of course someone had to write the massively-complex _command_offset function so lucky me I guess?
Anyways, thank you for reading the story of me messing around and hopefully this will be helpful to some other person in the future.
I'm defining a variable as a composition of other variables and some text, and I'm trying to get this variable to not expand its containing variables on the assigning. But I want it to expand when called later. That way I could reuse the same template to print different results as the inner variables keep changing. I'm truing to avoid eval as much as possible as I will be receiving some of the inner variables from third parties, and I do not know what to expect.
My use case, as below, is to have some "calling stack" so I can log all messages with the same format and keep a record of the script, function, and line of the logged message in some format like this: script.sh:this_function:42.
My attempted solution
called.sh:
#!/bin/bash
SCRIPT_NAME="`basename "${BASH_SOURCE[0]}"`"
CURR_STACK="${SCRIPT_NAME}:${FUNCNAME[0]}:${LINENO[0]}"
echo "${SCRIPT_NAME}:${FUNCNAME[0]}:${LINENO[0]}"
echo "${CURR_STACK}"
echo
function _func_1 {
echo "${SCRIPT_NAME}:${FUNCNAME[0]}:${LINENO[0]}"
echo "${CURR_STACK}"
}
_func_1
So, I intend to get the same results while printing the "${CURR_STACK}" as when printing the previous line.
If there is some built-in or other clever way to log this 'call stack', by all means, let me know! I'll gladly wave my code good-bye, but I'd still like to know how to prevent the variables from expanding right away on the assigning of CURR_STACK, but still keep them able to expand further ahead.
Am I missing some shopt?
What I've tried:
Case 1 (expanding on line 4):
CURR_STACK="${SCRIPT_NAME}:${FUNNAME[0]}:${LINENO[0]}"
CURR_STACK="`echo "${SCRIPT_NAME}:${FUNCNAME[0]}:${LINENO[0]}"`"
CURR_STACK="`echo "\${SCRIPT_NAME}:\${FUNCNAME[0]}:\${LINENO[0]}"`"
called.sh::7 <------------------| These are control lines
called.sh::4 <---------------. .------------| With the results I expect to get.
X
called.sh:_func_1:12 <---´ `-------| Both indicate that the values expanded
called.sh::4 <-------------------------| on line 4 - when CURR_STACK was set.
Case 2 (not expanding at all):
CURR_STACK="\${SCRIPT_NAME}:\${FUNNAME[0]}:\${LINENO[0]}"
CURR_STACK=\${SCRIPT_NAME}:\${FUNCNAME[0]}:\${LINENO[0]}
CURR_STACK="`echo '${SCRIPT_NAME}:${FUNCNAME[0]}:${LINENO[0]}'`"
called.sh::7
${SCRIPT_NAME}:${FUNNAME[0]}:${LINENO[0]} <-------.----| No expansion at all!...
/
called.sh::12 /
${SCRIPT_NAME}:${FUNNAME[0]}:${LINENO[0]} <----´
Shell variables are store plain inert text(*), not executable code; there isn't really any concept of delayed evaluation here. To make something that does something when used, create a function instead of a variable:
print_curr_stack() {
echo "$(basename "${BASH_SOURCE[1]}"):${FUNCNAME[1]}:${BASH_LINENO[0]}"
}
# ...
echo "We are now at $(print_curr_stack)"
# Or just run it directly:
print_curr_stack
Note: using BASH_SOURCE[1] and FUNCNAME[1] gets info about context the function was run from, rather than where it is in the function itself. But for some reason I'm not clear on, BASH_LINENO[1] gets the wrong info, and BASH_LINENO[0] is what you want.
You could also write it to allow the caller to specify additional text to print:
print_curr_stack() {
echo "$#" "$(basename "${BASH_SOURCE[1]}"):${FUNCNAME[1]}:${BASH_LINENO[0]}"
}
# ...
print_curr_stack "We are now at"
(* There's an exception to what I said about variables just contain inert text: some variables -- like $LINENO, $RANDOM, etc -- are handled specially by the shell itself. But you can't create new ones like this except by modifying the shell itself.)
Are you familiar with eval?
$ a=this; b=is; c=a; d=test;
$ e='echo "$a $b $c $d"';
$ eval $e;
this is a test
$ b='is NOT'; # modify one of the variables
$ eval $e;
this is NOT a test
$ f=$(eval $e); # capture the value of the "eval" statement
$ echo $f;
this is NOT a test
My project (port from ksh) use some directories as autoloadable functions.
In those directories each filenames as the name of a function declared inside the file, sourcing that file to declare (implement) the function. Each directories could be considered a 'package' that augment the bash builtin set via functions. I have about 20 packages, and the number of functions per package can be significant (can reach 30 in some packages).
The bash documentation includes an example implementation of autoloading:
https://www.apt-browse.org/browse/ubuntu/trusty/main/all/bash-doc/4.3-6ubuntu1/file/usr/share/doc/bash/examples/functions/autoload.v2
However, that implementation requires the set of potentially-autoloadable functions to be known (and enumerated) at shell startup time.
Is an implementation that doesn't have that limitation possible?
Well, after asking, my turn to 'give' :) to the SO community. I investigated this auto load feature I need and came up with 2 implementations, I provide one them here, so some may suggest enhancements or point out bugs. I'll post the second one on a second post.
The 2 implementations will runs some test cases, so before presenting the implementations I present the common test cases. We have 2 directories a1/ and a2/ that host function definitions located in file with same name as the function, each dir could be considered a 'package' dir containing functions for this package, and then function in there are namespaced with the package name (dir name), with few exception for the test purpose.
./a1/ac_f3::
function ac_f3
{ echo "In a1 ac_f3() : args=$#"
}
./a1/a1_f1::
function a1_f1
{ echo "In a1_f1() : args=$#"
}
./a1/a1_f2::
function a1_f22
{ echo "In a1_f2() : args=$#"
}
./a2/ac_f3::
function f3
{ echo "In a2 ac_f3() : args=$#"
}
./a2/a2_f1::
function a2_f1
{ echo "In a2_f1() : args=$#"
}
ac_f3 is a function that is not namespaced, and then common to both dir a1/ a2/ yet with different implementation, this is to demonstrate the $FPATH precedence.
a1_f2 is a bogus one it doesn't implement the function a1_f2() and then we must fail gracefully.
a1_f1, a2_f1, simply implement a1_f1() a2_f2(), and must be found and executed.
command_not_found_handle implementation
Thanks Charles for bringing the command_not_found_handle option, because, surely auto loadable function are related to the fact that a 'command' has not been found and then we try to find a auto loadable to load and execute.
But amazingly, the bash shell has an interesting "feature", i.e some undocumented behavior.
Bash doc says.
If the search is unsuccessful, the shell searches for a defined
shell function named command_not_found_handle. If that
function exists, it is invoked with the original command and
the original command's arguments as its arguments, and the
function's exit status becomes the exit status of the shell.
If that function is not defined, the shell prints an error
message and returns an exit status of 127.
This is misleading because here we talk about the command_not_found_handle() function invocation, and then we may infere 'from the shell context' and this is not the case.
In the shell logic, we failed to get an alias, then fail to get a function, then failed to get an 'external to the shell' program, and the shell is already in a sub-shell creation mode, so command_not_found_handle() is invoked but in a subshell. not the shell context. This could be OK, but the 'funny feature' here is that the sub-process created is not clean, its $$ and $PPID are not set correctly, may be this will be fixed one day. To exhibit this bash feature we can do
function command_not_found_handle
{ echo $$ ; sh -c 'echo $PPID'
}
PW$ # In a shell context invocation
PW$ command_not_found_handle
2746
2746
PW$ # In a subshell invocation (via command not found)
PW$ qqq
2746
3090
Back to our autoload feature, this mean we want to install more functions in a shell instance, nothing that can be done in a subshell, so basically command_not_found_handle() is of tiny help and can do nothing beside signal its parent we got entered (then a command was not found), we will exploit this feature in our implementation.
# autoload
# This file must be sourced
# - From your rc files if you need autoloadable fuctions from your
# interactive shell
# - From any script that need autoloadable functions.
#
# The FPATH must be set with a set of dirs/ where to look to find
# file name match the function name to source and execute.
#
# Note that if FPATH is exported, this is a way to export functions to
# script subshells
# Create a default command_not_found_handle if none exist
declare -F command_not_found_handle >/dev/null ||
function command_not_found_handle { ! echo bash: $1 command not found>&2; }
# Rename current command_not_found_handle
_cnf_body=$(declare -f command_not_found_handle | tail -n +2)
eval "function _cnf_prev $_cnf_body"
# Change USR1 to your liking
CNF_SIG=USR1
function autoload
{ declare f=$1 ; shift
declare d s
for d in $(IFS=:; echo $FPATH)
do s=$d/$f
[ -f $s -a -r $s ] &&
{ . $s
declare -F $f >/dev/null ||
{ echo "$s exist but don't define $f" >&2 ; return 127
}
$f "$#" ; return
}
done
_cnf_prev $f "$#"
}
trap 'autoload ${BASH_COMMAND[#]}' $CNF_SIG
function command_not_found_handle
{ kill -$CNF_SIG $$
}
WARNING, if you ever use this 'autoload' file be prepared for bash fix, it may one day reflect the real $$ $PPID, in which case you will need to fix the above snippet with
$PPID instead of $$.
Results.
PW$ . /path/to/autoload
PW$ FPATH=a1:a2
PW$ a1_f1 11a 11b 11c
In a1_f1() : args=11a 11b 11c
PW$ a2_f1 21a 21b 21c
In a2_f1() : args=21a 21b 21c
PW$ a1_f2 12a 12b 12c
a1/a1_f2 exist but don't define a1_f2
PW$ ac_f3 c3a c3b c3c
In a1 ac_f3() : args=c3a c3b c3c
PW$ qqq
Command 'qqq' not found, did you mean:
command 'qrq' from snap qrq (0.3.1)
command 'qrq' from deb qrq
See 'snap info <snapname>' for additional versions.
What we got here is correct, a1_f1() a2_f1() are found, loaded, executed.
a1_f2() is nowhere to be found, despite having a file that could host it.
The qqq invocation display the chaining of the handlers, going function autoload fist, then the ubuntu command-not-found package (if installed) meaning we are not loosing the command_not_found_handle() user experience.
Note there is no 'admin' functions here like adding/removing/reloading functions.
Adding is a matter of setting file in dirs present in $FPATH
Removing is a matter of removing the source file and unset -f the function
Reloading is a matter of editing the source file and unset -f the function.
Reloading function can be pretty neat during development in interactive shell, but all this can be done with a simple
unset -f funcname, so basically you edit your source file. unset the function, then call it, you get the latest. Same may happen in a script daemon, one could implement a signal to the daemon and the trap handler would simply unset a set of functions that would then be reloaded without stopping/restarting the daemon.
Another feature here is that shell 'package' are possible, i.e a source file may implement 'many' functions, some are the external API, other are internal to the package, since all is flat in the shell, function are namespaced, and then each external API functions (albeit documented) can be hard linked to the same file. The first external API used will load all the package functions.
In my project, the documentation is extracted from the packages sources, and then hardlink are inferred and build at this time.
PROs and CONs
PROs
Here we got a light signature in the autoload sourcing, i.e from scripts or from bash rc file (interactive), the define of the autoload() is modest.
It is very dynamic, in the sense that function loading and executing is really deferred until really needed.
CONs
It grabs a signal number, that would not be necessary should the command_not_found_handle() be a real function called from the shell context, this could happen one day.
It is implemented on a bash feature that may move (wrong, $$ $PPID) then need maintenance on the moving target.
Conclusion
This implementation is OK for me (I Don't care loosing SIGUSR1). The ideal solution would be that command_not_found_handle() would be cleanly implemented and then called in the shell context. The a similar implementation would be possible without any signal.
This is a second implementation to avoid the signal usage seen in the previous implementation and the usage of the command_not_found_handle() that seems not completly stable.
autoload::
function autoload
{ local d="$1" && [ "$1" ] && shift && autoload "$#"
local identifier='^[_a-zA-Z][_a-zA-Z0-9]*$'
[ -d "$d" -a -x "$d" ] && cd "$d" &&
{ for f in *
do [[ $f =~ $identifier ]] && alias $f=". $PWD/$f;unalias $f;$f"
done
cd ->/dev/null 2>&1
}
}
autoload $# $(IFS=:; echo $FPATH)
Here again we got to source this autolaod file either in rc file or in scripts.
The usage of FPATH is not really needed (see Notes for more details on FPATH)
So basically the idea is to source the autoload file along with a set of directories to look for.
PW$ . /path/to/autoload a1 a2
PW$ alias | grep 'a[12c]_*'
alias a1_f1='. /home/phi/a1/a1_f1;unalias a1_f1;a1_f1'
alias a1_f2='. /home/phi/a1/a1_f2;unalias a1_f2;a1_f2'
alias a2_f1='. /home/phi/a2/a2_f1;unalias a2_f1;a2_f1'
alias ac_f3='. /home/phi/a1/ac_f3;unalias ac_f3;ac_f3'
PW$ declare -F | grep 'a[12c]_*'
After the autoload sourcing, we got all the alias defined and no functions.
This is a bit heavier than the previous implementation, yet pretty lightweight, alias are not costly to create in the shell, even with hundred of them.
PW$ a1_f1 11a 11b 11c
In a1_f1() : args=11a 11b 11c
PW$ a2_f1 21a 21b 21c
In a2_f1() : args=21a 21b 21c
PW$ alias | grep 'a[12c]_*'
alias a1_f2='. /home/phi/a1/a1_f2;unalias a1_f2;a1_f2'
alias ac_f3='. /home/phi/a1/ac_f3;unalias ac_f3;ac_f3'
PW$ declare -F | grep 'a[12c]_*'
declare -f a1_f1
declare -f a2_f1
Here we see that a1_f1() and a2_f2() are then loaded and executed, they are removed from the alias list and added in the function list.
PW$ a1_f2 12a 12b 12c
a1_f2: command not found
PW$ ac_f3 c3a c3b c3c
In a1 ac_f3() : args=c3a c3b c3c
PW$ qqq
Command 'qqq' not found, did you mean:
command 'qrq' from snap qrq (0.3.1)
command 'qrq' from deb qrq
See 'snap info <snapname>' for additional versions.
Here we see that a1_f2() is not found, not well reported as in the previous implementation.
ac_f3() is the one from a1/ as expected.
qqq still provide the command-not-found distro package result if installed ( normal we didn't mess with command_not_found_handle() )
PROs and CONs
PROs
Not sitting on a bash bug, i.e could live for a while after bash updates.
CONs
A little bit heavier than previous implementation, yet acceptable.
Much simpler, well may be not simpler, but surely shorter than proposed examples in bash documentation, and a bit more lazy, i.e function are loaded only when necessary (not the aliases though)
Multi function 'package' files along with hardlink for external API exposure is less performing, because each external API function (hardlink) will trig a reload of the file, unless the package file is well written removing all the excess aliases after loading.
I found this code in an autoconf configure script. What is the following code trying to do?
if ${am_cv_autoconf_installed+:} false; then :
$as_echo_n "(cached) " >&6
else
Lots of stuff going on here. Let's break it down.
First of all, the syntax ${var+foo} is a common idiom for checking whether the variable var has been defined. If var is defined, then ${var+foo} will expand to the string foo. Otherwise, it will expand to an empty string.
Most commonly (in bash, anyway), this syntax is used as follows:
if [ -n "${var+foo}" ]; then
echo "var is defined"
else
echo "var is not defined"
fi
Note that foo is just any arbitrary text. You could just as well use x or abc or ilovetacos.
However, in your example, there are no brackets. So whatever ${am_cv_autoconf_installed+:} expands to (if anything) will be evaluated as a command. As it turns out, : is actually a shell command. Namely, it's the "null command". It has no effect, other than to set the command exit status to 0 (success). Likewise, false is a shell command that does nothing, but sets the exit status to 1 (failure).
So depending on whether the variable am_cv_autoconf_installed is defined, the script will then execute one of the following commands:
: false
-OR-
false
In the first case, it calls the null command with the string "false" as an argument, which is simply ignored, causing the if statement to evaluate to true. In the second case, it calls the false command, causing the if statement to evaluate to false.
So all this is really doing is checking whether am_cv_autoconf_installed is defined. If this were just an ordinary bash script and didn't require any particular level of portability, it would have been a lot simpler to just do:
if [ -n "${am_cv_autoconf_installed+x}" ]; then
However, since this is a configure script, it was no doubt written this way for maximum portability. Not all shells will have the -n test. Some may not even have the [ ] syntax.
The rest should be fairly self-explanatory. If the variable is defined, the if statement evaluates to true (or more accurately, it sets the exit status to 0), causing the $as_echo_n "(cached) " >&6 line to execute. Otherwise, it does whatever is in the else clause.
I'm guessing $as_echo_n is just the environment-specific version of echo -n, which means it will print "(cached) " with no trailing newline. The >&6 means the output will be redirected to file descriptor 6 which presumably is set up elsewhere in the script (probably a log file or some such).
Here's the code snippet from a shell script. (It's from MPFR library's configure script and it starts with #!/bin/sh. The original script is over 17000 lines long.. It's used when building gcc.)
Because I have so many questions in a short piece of code, I have embedded my questions in the code. Please can somebody explain to me why the code is like this? Also, though I have a vague idea, I would appreciate if someone could explain what this code is doing (I understand it will be difficult because it's only a part of a big script).
if { { ac_try="$ac_link"
# <---- question 1 : why is the first curly bracket used for if condition? (probably just for grouping and using the last return code)
# <---- question 2 : Is this second bracket for locally used code(probably)?
case "(($ac_try" in # <---- question 3 : what is this "((" symbol?
*\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
*) ac_try_echo=$ac_try;;
esac
eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\""
$as_echo "$ac_try_echo"; } >&5 # <---- question 4 : what is this >&5 redirection? I know >&{1,2,3} but not 5.
(eval "$ac_link") 2>&5
# <----- question 5 : why use sub-shell here? not to use eval result?
ac_status=$?
$as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
test $ac_status = 0; }; then : # <---- question 6 : is this ':'(nop) here ?
....
some commands
....
else
....
some commands
....
fi
From Bash man page:
{ list; }
list is simply executed in the current shell environment. list
must be terminated with a newline or semicolon. This is known
as a group command. The return status is the exit status of
list. Note that unlike the metacharacters ( and ), { and } are
reserved words and must occur where a reserved word is permitted
to be recognized. Since they do not cause a word break, they
must be separated from list by whitespace or another shell
metacharacter.
{} is just to list a few commands to run, very much like cmd1; cmd2; cmd3. For example, if you write cmd1 ; cmd2 | cmd3, do you mean {cmd1; cmd2;} | cmd3 or cmd1; {cmd2 | cmd3;}.
{{ }} is just nested command list, easy: e.g. {cmd1; cmd2; {cmd3; cmd4;}; }
For question 3, (( is just in a source string to be matched with the following patterns. If you are asking why it is used, we need possible values of $ac_try to analyze why. Honestly, I don't see many shell scripts purposely adding (( in front of a source string to be matched for patterns.
For question 4,
>&5: if file descriptor 5 is not yet created (i.e. mentioned in any part of the script... => be careful, you need to care the scope, some codes runs in sub-shell, which is counted as a sub-shell context/scope), create an unnamed file (well, temp file, if you like), with descriptor 5. This file can be used in other part of the script as an input.
For example, see the part mentioning "exchanges STDIN and STDOUT" in my answer to another question here.
For question 5, the eval, I am not quite sure, just a quick guess (and it depends on what command it evals) by providing you an example why sub-shell makes some differences:
cmd="Foo=1; ls"
(eval $cmd) # this command runs in sub-shell and thus $Foo in current shell will not be changed.
eval $cmd # this command runs in current shell and thus $Foo is changed, and it will affect all subsequent commands.
For question 6, look carefully at the man page I mentioned at top of the answer, the {} list syntax, require a final ;. i.e. {cmd1; cmd2 ; } The last ; is required.
--- UPDATE ---
Question 6: Sorry for not seeing the colon... :-)
It's no op: see this link.