Reputation: 2584
This question differs in that the classic "use a function" answer WILL NOT work. Adding a note to an existing Alias question is equivalent to sending a suggestion e-mail to Yahoo.
I am trying to write macros to get around BASH's horrendous IF syntax. You know, the [, [[, ((...BASH: the PHP of flow control...just add another bracket. I'm still waiting for the "(((((((" form. Not quite sure why BASH didn't repurpose "(", as "(" has no real semantics at the if statement.
The idea is to have named aliases for [, [[ and (( , as each one of these durned test-ish functions has a frustratingly different syntax. I honestly can never remember which is which (how COULD you? It's completely ad hoc!), and good luck trying to google "[[".
I would then use the names as a mnemonic, and the alias to get rid of the completely awful differences in spacing requirements. Examples: "whatdoyoucallthisIf" for "((", "shif" (for shell if), "mysterydoublesquarebacketif" for that awful [[ thing which seems to mostly do the same thing as [, only it doesn't.
Thus, I MUST have something of the form:
alias IFREPLACEMENT="if [ \$@ ]; then"
But obviously not \$@, which would just cement in the current argument list to the shell running the alias.
Functions will not work in this case, as the function:
function IFREPLACEMENT {
if [ $@ ]; then
}
is illegal.
In CSH, you could say alias abc blah blah !* !1, etc. Is there ANYTHING in BASH that is similar (no, !* doesn't work in BASH)?
Or am [ "I just out of luck" ]; ?
As an aside, here are some of the frustrating differences involving test-ish functions in BASH that I am trying to avoid by using well-defined aliases that people would have to use instead of picking the wrong "[[", "[" or "((":
Upvotes: 0
Views: 363
Reputation: 438073
Update: Based on feedback from @konsolebox, the recommendation is now to always use [[...]]
for both simplicity and performance (the original answer recommended ((...))
for numerical/Boolean tests).
@Oliver Charlesworth, in a comment on the question, makes the case for not trying to hide the underlying bash
syntax - and I agree.
You can simplify things with the following rules:
[[ ... ]]
for tests.
[ ... ]
if POSIX compatibility is a must. If available, [[ ... ]]
is always the better choice (fewer surprises, more features, and almost twice as fast[1]). $
-prefixed variable references - for robustness and simplicity (you do pay a slight performance penalty for double-quoting, though1) - e.g., "$var"
; see the exceptions re the RHS of ==
and =~
below.[[
/ ((
or ]]
/ ))
)=
in variable assignments. These rules are more restrictive than they need to be - in the interest of simplification.
Tips and pitfalls:
[[ ... ]]
, you must use -eq
, -gt
, -ge
, -lt
, -le
, because ==
, <
, <=
, >
, >=
are for lexical comparison.
[[ 110 -gt 2 ]] && echo YES
==
with pattern matching (globbing), either specify the entire RHS as an unquoted string, or, at least leave the special globbing characters unquoted.
[[ 'abc' == 'a'* ]] && echo YES
=~
requires that either the entire RHS be unquoted, or at least leave the special regex chars. unquoted - if you use a variable to store the regex - as you may have to in order to avoid bugs with respect to \
-prefixed constructs on Linux - reference that variable unquoted.
[[ 'abc' =~ ^'a'.+$ ]] && echo YES
re='^a.+$'; [[ 'abc' =~ $re ]] && echo YES # *unquoted* use of var. $re
An alternative to [[ ... ]]
, for purely numerical/Boolean tests, is to use arithmetic evaluation, ((...))
, whose performance is comparable to [[
(about 15-20% slower1); arithmetic evaluation (see section ARITHMETIC EVALUATION
in man bash
):
+
, -
, *
, /
, **
, %
, ...++
/ --
).No $
prefix required for variable references.
$
in 2 scenarios:
var=010; (( 10#$var > 9 )) && echo YES # mandate number base 10
var=v10; (( ${var#v} > 9 )) && echo YES # strip initial 'v'
((...)
, curiously, expands a variable name without $
recursively, until its value is not the name of an existing variable anymore:var1=10; var2=var1; (( var2 > 9 )) && echo YES
var2
expands to 10
(!)Has laxer whitespace rules.
v1=0; ((v2 = 1 + ++v1)) && echo YES # -> $v1 == 1, $v2 == 2
bash
, you'll have to weigh its added features against having to remember an extra set of rules. You also pay a slight performance penalty1.You can even cram arithmetic expressions, including assignments, into [[
conditionals that are based on numeric operators, though that may get even more confusing; e.g.:
v1=1 v2=3; [[ v1+=1 -eq --v2 ]] && echo TRUE # -> both $v1 and $v2 == 2
Note: In this context, by 'quoting' I mean single- or double-quoting an entire string, as opposed to \
-escaping individual characters in a string not enclosed in either single- or double quotes.
1: The following code - adapted from code by @konsolebox - was used for performance measurements:
Note:
[[
being nearly twice as fast as [
(factor around 1.9) is based on:
$
-prefixed variable references in [[
(using double-quoted variable references slows things down somewhat)((
is slower than [[
with unquoted, $
-prefixed variable on both platforms: about 15-20% on OSX, around 30% on Ubuntu. On OSX, using double-quoted, $
-prefixed variable references is actually slower, as is not using the $
prefix at all (works with numeric operators). By contrast, on Ubuntu, ((
is slower than all ]]
variants.#!/usr/bin/env bash
headers=( 'test' '[' '[[/unquoted' '[[/quoted' '[[/arithmetic' '((' )
iterator=$(seq 100000)
{
time for i in $iterator; do test "$RANDOM" -eq "$RANDOM"; done
time for i in $iterator; do [ "$RANDOM" -eq "$RANDOM" ]; done
time for i in $iterator; do [[ $RANDOM -eq $RANDOM ]]; done
time for i in $iterator; do [[ "$RANDOM" -eq "$RANDOM" ]]; done
time for i in $iterator; do [[ RANDOM -eq RANDOM ]]; done
time for i in $iterator; do (( RANDOM == RANDOM )); done
} 2>&1 | fgrep 'real' | { i=0; while read -r line; do echo "${headers[i++]}: $line"; done; } | sort -bn -k3.3 | awk 'NR==1 { baseTime=substr($3,3) } { time=substr($3,3); printf "%s %s%%\n", $0, (time/baseTime)*100 }' | column -t
Outputs times from fastest to slowest, with slower times also expressed as a percentage of the fastest time.
Upvotes: 4
Reputation: 246837
Sure, functions will work, but not like a macro:
function IFREPLACEMENT {
[[ "$@" ]]
}
IFREPLACEMENT "$x" = "$y" && {
echo "the same
}
FWIW, here's a brutal way to pass arguments to an alias.
$ alias enumerate='bash -c '\''for ((i=0; i<=$#; i++)); do arg=${!i}; echo $i $arg; done'\'
$ enumerate foo bar baz
0 foo
1 bar
2 baz
Clearly, because a new bash shell is spawned, whatever you do won't have any effect on the current shell.
Upvotes: 3