I am trying to set an alias in a script and then execute the alias later on in the script. I've verified that the file path that the alias contains is valid, and I've also set the shell script to expand aliases as well, yet the script still refuses to use the alias. What could I be doing incorrectly here?
Script:
#set location of parallel script and replace ~ with $HOME if necessary
parallellocation="~/newnnm/parallel"
parallellocation="${parallellocation/#\~/$HOME}"
#If the parallellocation variable is set and a parallel command is not currently available,
#proceed with setting an alias that points to the parallellocation variable
if [ -r "$parallellocation" ] && ! command -v parallel &>/dev/null; then
shopt -s expand_aliases
alias parallel="$parallellocation"
parallel
fi
Sample output:
./processlocations_new2.sh
./processlocations_new2.sh: line 98: parallel: command not found
As reflected in the comment record on the question, bash seems not to honor alias definitions or setting of the alias_expand option within the scope of an if block or other compound command. The Bash Manual explains this:
The rules concerning the definition and use of aliases are somewhat
confusing. Bash always reads at least one complete line of input
before executing any of the commands on that line. Aliases are
expanded when a command is read, not when it is executed. Therefore,
an alias definition appearing on the same line as another command does
not take effect until the next line of input is read. The commands
following the alias definition on that line are not affected by the
new alias. This behavior is also an issue when functions are executed.
Aliases are expanded when a function definition is read, not when the
function is executed, because a function definition is itself a
command. As a consequence, aliases defined in a function are not
available until after that function is executed. To be safe, always
put alias definitions on a separate line, and do not use alias in
compound commands.
(Emphasis added.) The comments do not refer directly to shell options, but the same logic that says alias definitions within a compound command do not apply within the same compound command also implies that it is the value of the expand_aliases option in effect when the compound command is read that applies.
The question arose as to how to use a shell function instead of an alias for this purpose. Here's one way:
altparallel="$HOME/newnnm/parallel"
parallellocation=
if command -v parallel >/dev/null 2>&1; then
parallellocation="command parallel"
elif [[ -x "$altparallel" ]]; then
parallellocation="$altparallel"
fi
# Runs parallel with the given arguments. Uses the 'parallel' command
# found in the path if there is one, or a local one designated by
# $altparallel if that exists and is executable. Exit status is that of
# 'parallel', or 1 if no 'parallel' command is available.
parallel() {
[[ -z "$parallellocation" ]] && return 1
# expansion of $parallellocation is intentionally unquoted
$parallellocation "$#"
}
You source that from your environment setup scripts to get a parallel function defined that does what you want.
On the third hand, if all you want is a script that runs one version of parallel or the other, directly, then you don't need either a function or an alias. Just figure out which you want to run, and run it, something like:
altparallel="$HOME/newnnm/parallel"
if command -v parallel >/dev/null 2>&1; then
parallel "$#"
elif [[ -x "$altparallel" ]]; then
"$altparallel" "$#"
else
exit 1
fi
Aliases are a parse time feature. They work by substituting one string for another during parsing.
Commands are entirely parsed before they're executed, and this includes compound commands like if.
The effect of this is that any changes to the parser, like setting aliases, will not take effect in any of the possibly nested compound commands where the setting takes place.
For example, if you wrap your entire script in {...}, no aliases will work because it's now a giant compound command.
This is yet another reason why you should never use aliases outside .bashrc, and even then just sparingly. Use functions.
You can enable/disable alias expansion with shopt within a bash script/command line.
$ shopt -u expand_aliases
-s Set
-u Unset
You can also use unalias to remove specific aliases (if you know them)
$ unalias <aliasname>
Be aware when you're using aliases in BASH srcipts, they aren't going to expand inside conditional constructs.
So while this one would work inside of a script:
#!/bin/bash
shopt -e expand_aliases
alias list1='ls -lhS'
list1
this one won't:
#!/bin/bash
shopt -e expand_aliases
alias list2='ls -lah'
if [ 1 ]; then
list2
fi
So as the others pointed out in the comment section, use functions, or use eval to execute the command string stored in $parallellocation:
eval $parallellocation
Related
I have a script with an alias _test. It works fine, but before printing the output, it does <arguments>: command not found. For example, _test -h gives line 49: -h: command not found
This is a minimal example:
alias _test='
echo Hi
'
shopt -s expand_aliases
_test -h
EDIT: For those asking about using functions, I did in fact, used to have a function instead -- but it started to cause recursion problems. I just wanted something similar to a macro -- something that acts as if the text was inserted into the script.
EDIT 2: I just realized why I kept having recursion with my function/alias. I fixed it, and I switched back to a function, but this question may help someone else.
Remove the newlines. As written, _test -h expands to this, with a blank line above and below the echo:
echo Hi
-h
Make it a one-line alias:
alias _test='echo Hi'
In general, though, avoid aliases. They're really intended for convenience in interactive shells. In a script—or heck, even in interactive shells—it's better to use functions instead. For example:
_test() {
echo Hi "$#"
}
For those asking about using functions, I did in fact, used to have a function instead -- but it started to cause recursion problems. I just wanted something similar to a macro -- something that acts as if the text was inserted into the script.
Were you trying to wrap an existing command, like this?
alias ls='ls --color=auto -F'
If so you can use command to prevent a function calling itself recursively. The equivalent function would be:
ls() {
command ls --color=auto -F "$#"
}
command ls calls the ls command rather than the ls function we've just defined so we don't get stuck in an infinite recursive loop.
Thanks to Kugelman's answer, I was able to solve it with
alias _test='
echo Hi
true'
shopt -s expand_aliases
_test -h
I have a bash script a.sh that looks like this:
#!/bin/bash
echo $#
echo $1
and a script b.sh that looks like this:
#!/bin/bash
source ./a.sh
If I call ./a.sh I'm correctly getting 0 and an empty line as output. When calling ./a.sh blabla I'm getting 1 and blabla as output.
However when I call ./b.sh blabla I'm also getting 1 and blabla as output, even though no argument was passed to a.sh from within b.sh.
This seems to be related to the use of source (which I have to use since in my real use case, a.sh exports some variables). How can I avoid arguments from b.sh being propagated to a.sh? I thought about using eval $(a.sh) but this makes my echo statements in a.sh fail. I thought of using shift to consume the arguments from b.sh before calling a.sh but I don't necessarily know how many arguments there are.
The root of the problem is an anomaly in how the source command works. From the bash man page, in the "Shell Builtin Commands" section:
. filename [arguments]
source filename [arguments]
[...] If any arguments are supplied, they become the positional parameters when filename is executed. Otherwise the positional parameters are unchanged.
...which means you can override the main script's arguments by supplying different arguments to the sourced script, but you can't just not pass arguments to it.
Fortunately, there's a workaround; just source the script in a context where there are no arguments:
#!/bin/bash
wrapperfunction() {
source ./a.sh
}
wrapperfunction
Since no arguments are supplied to wrapperfunction, inside it the arg list is empty. Since a.sh's commands are run in that context, the arg list is empty there as well. And variables assigned inside a.sh are available outside the function (unless they're declared as local or something similar).
(Note: I tested this in bash, zsh, dash, and ksh93, and it works in all of them -- well, except that dash doesn't have the source command, so you have to use . instead.)
Update: I realized you can write a generic wrapper function that allows you to specify a filename as an argument:
sourceWithoutArgs() {
local fileToSource="$1"
shift
source "$fileToSource"
}
sourceWithoutArgs ./a.sh
The shift command removes the filename from the function's arg list, so it's empty when the file actually gets sourced. Well, unless you passed additional arguments to the function, in which case those will be in the arg list and will get passed on... so you can actually use this function to replace both the without-args and the with-args usage of source.
(This works in bash and zsh. If you want to use it in ksh, you have to remove local; and to use it in dash, replace source with .)
You can even keep passing normal arguments using
source() {
local f="${1}"; shift;
builtin source "${f}" "${#}"
}
It is also possible to check from the sourced file what arguments have actually been given
# in x.bash, a file meant to be sourced
# fix `source` arguments
__ARGV=( "${#}" )
__shopts=$( shopt -p ) # save shopt
shopt -u extdebug
shopt -s extdebug # create BASH_ARGV
# no args have been given to `source x.bash`
if [[ ${BASH_ARGV[0]} == "${BASH_SOURCE[0]}" ]]; then
__ARGV=() # clear `${__ARGV[#]}`
fi
eval "${__shopts}" # restore shopt
unset __shopts
# Actual args are in ${__ARGV[#]}
I want to inject a transparent wrappering command on each shell command in a make file. Something like the time shell command. ( However, not the time command. This is a completely different command.)
Is there a way to specify some sort of wrapper or decorator for each shell command that gmake will issue?
Kind of. You can tell make to use a different shell.
SHELL = myshell
where myshell is a wrapper like
#!/bin/sh
time /bin/sh "$0" "$#"
However, the usual way to do that is to prefix a variable to all command calls. While I can't see any show-stopper for the SHELL approach, the prefix approach has the advantage that it's more flexible (you can specify different prefixes for different commands, and override prefix values on the command line), and could be visibly faster.
# Set Q=# to not display command names
TIME = time
foo:
$(Q)$(TIME) foo_compiler
And here's a complete, working example of a shell wrapper:
#!/bin/bash
RESULTZ=/home/rbroger1/repos/knl/results
if [ "$1" == "-c" ] ; then
shift
fi
strace -f -o `mktemp $RESULTZ/result_XXXXXXX` -e trace=open,stat64,execve,exit_group,chdir /bin/sh -c "$#" | awk '{if (match("Process PID=\d+ runs in (64|32) bit",$0) == 0) {print $0}}'
# EOF
I don't think there is a way to do what you want within GNUMake itself.
I have done things like modify the PATH env variable in the Makefile so a directory with my script linked to all name the bins I wanted wrapped was executed rather than the actual bin. The script would then look at how it was called and exec the actual bin with the wrapped command.
ie. exec time "$0" "$#"
These days I usually just update the targets in the Makefile itself. Keeping all your modifications to one file is usually better IMO than managing a directory of links.
Update
I defer to Gilles answer. It's a better answer than mine.
The program that GNU make(1) uses to run commands is specified by the SHELL make variable. It will run each command as
$SHELL -c <command>
You cannot get make to not put the -c in, since that is required for most shells. -c is passed as the first argument ($1) and <command> is passed as a single argument string as the second argument ($2).
You can write your own shell wrapper that prepends the command that you want, taking into account the -c:
#!/bin/sh
eval time "$2"
That will cause time to be run in front of each command. You need eval since $2 will often not be a single command and can contain all sorts of shell metacharacters that need to be expanded or processed.
I wish to source a script, print the value of a variable this script defines, and then have this value be assigned to a variable on the command line with command substitution wrapping the source/print commands. This works on ksh88 but not on ksh93 and I am wondering why.
$ cat typeset_err.ksh
#!/bin/ksh
unset _typeset_var
typeset -i -r _typeset_var=1
DIR=init # this is the variable I want to print
When run on ksh88 (in this case, an AIX 6.1 box), the output is as follows:
$ A=$(. ./typeset_err.ksh; print $DIR)
$ echo $A
init
When run on ksh93 (in this case, a Linux machine), the output is as follows:
$ A=$(. ./typeset_err.ksh; print $DIR)
-ksh: _typeset_var: is read only
$ print $A
($A is undefined)
The above is just an example script. The actual thing I wish to accomplish is to source a script that sets values to many variables, so that I can print just one of its values, e.g. $DIR, and have $A equal that value. I do not know in advance the value of $DIR, but I need to copy files to $DIR during execution of a different batch script. Therefore the idea I had was to source the script in order to define its variables, print the one I wanted, then have that print's output be assigned to another variable via $(...) syntax. Admittedly a bit of a hack, but I don't want to source the entire sub-script in the batch script's environment because I only need one of its variables.
The typeset -r code in the beginning is the error. The script I'm sourcing contains this in order to provide a semaphore of sorts--to prevent the script from being sourced more than once in the environment. (There is an if statement in the real script that checks for _typeset_var = 1, and exits if it is already set.) So I know I can take this out and get $DIR to print fine, but the constraints of the problem include keeping the typeset -i -r.
In the example script I put an unset in first, to ensure _typeset_var isn't already defined. By the way I do know that it is not possible to unset a typeset -r variable, according to ksh93's man page for ksh.
There are ways to code around this error. The favorite now is to not use typeset, but just set the semaphore without typeset (e.g. _typeset_var=1), but the error with the code as-is remains as a curiosity to me, and I want to see if anyone can explain why this is happening.
By the way, another idea I abandoned was to grep the variable I need out of its containing script, then print that one variable for $A to be set to; however, the variable ($DIR in the example above) might be set to another variable's value (e.g. DIR=$dom/init), and that other variable might be defined earlier in the script; therefore, I need to source the entire script to make sure I all variables are defined so that $DIR is correctly defined when sourcing.
It works fine for me in ksh93 (Version JM 93t+ 2009-05-01). If I do this, though:
$ . ./typeset_err.ksh
$ A=$(. ./typeset_err.ksh; print $DIR)
-ksh: _typeset_var: is read only
So it may be that you're getting that variable typeset -r in the current environment somehow.
Try this
A=$(ksh -c "./typeset_err.ksh && print \$DIR")
or
A=$(env -i ksh -c "./typeset_err.ksh && print \$DIR")
This question already has answers here:
Make a Bash alias that takes a parameter?
(24 answers)
Closed 2 years ago.
The community reviewed whether to reopen this question 12 months ago and left it closed:
Original close reason(s) were not resolved
How do I pass the command line arguments to an alias? Here is a sample:
alias mkcd='mkdir $1; cd $1;'
But in this case the $xx is getting translated at the alias creating time and not at runtime. I have, however, created a workaround using a shell function (after googling a little) like below:
function mkcd(){
mkdir $1
cd $1
}
Just wanted to know if there is a way to make aliases that accept CL parameters.
BTW - I use 'bash' as my default shell.
Just to reiterate what has been posted for other shells, in Bash the following works:
alias blah='function _blah(){ echo "First: $1"; echo "Second: $2"; };_blah'
Running the following:
blah one two
Gives the output below:
First: one
Second: two
You found the way: create a function instead of an alias. The C shell has a mechanism for doing arguments to aliases, but bash and the Korn shell don't, because the function mechanism is more flexible and offers the same capability.
You cannot in ksh, but you can in csh.
alias mkcd 'mkdir \!^; cd \!^1'
In ksh, function is the way to go. But if you really really wanted to use alias:
alias mkcd='_(){ mkdir $1; cd $1; }; _'
To quote the bash man page:
There is no mechanism for using arguments in the replacement text. If
arguments are needed, a shell function should be used (see FUNCTIONS
below).
So it looks like you've answered your own question -- use a function instead of an alias
You may also find this command useful:
mkdir dirname && cd $_
where dirname is the name of the directory you want to create
The easiest way, is to use function not alias. you can still call a function at any time from the cli. In bash, you can just add function name() { command } it loads the same as an alias.
function mkcd() { mkdir $1; cd $1 ;}
Not sure about other shells
I found that functions cannot be written in ~/.cshrc file. Here in alias which takes arguments
for example, arguments passed to 'find' command
alias fl "find . -name '\!:1'"
Ex: >fl abc
where abc is the argument passed as !:1
You actually can't do what you want with Bash aliases, since aliases are static. Instead, use the function you have created.
Look here for more information: http://www.mactips.org/archives/2008/01/01/increase-productivity-with-bash-aliases-and-functions/. (Yes I know it's mactips.org, but it's about Bash, so don't worry.)
This works in ksh:
$ alias -x mkcd="mkdir \$dirname; cd \$dirname;"
$ alias mkcd
mkcd='mkdir $dirname; cd $dirname;'
$ dirname=aaa
$ pwd
/tmp
$ mkcd
$ pwd
/tmp/aaa
The "-x" option make the alias "exported" - alias is visible in subshells.
And be aware of fact that aliases defined in a script are not visible in that script (because aliases are expanded when a script is loaded, not when a line is interpreted). This can be solved with executing another script file in same shell (using dot).
Here's a simple example function using Python. You can stick in ~/.bashrc.
You need to have a space after the first left curly bracket.
The python command needs to be in double quotes to get the variable substitution. Don't forget that semicolon at the end.
count(){ python -c "for num in xrange($1):print num";}
Example run:
$ count 6
0
1
2
3
4
5
$
An empty alias will execute its args:
alias DEBUG=