I would like to invoke alias from ruby code so as to test the alias which I programmatically inserted into the dotfile. Say for example, the alias is the following:
alias something="echo somethingelse"
I searched the web and found the solution for bash:
#solution for bash
system %(
source ~/.bash_profile
shopt -s expand_aliases
something
)
However, this does not work for zsh.
I tried to invoke the alias using the following code (and a combination of other commands) but to no avail.
system %(
exec zsh #this seems to source .zshrc
something #this does not work
)
I would like it to work for zsh too. How can I get it working for zsh? Does anyone have a suggestion? Thanks in advance!
I have also tested the following but they don't work:
system %(
# exec /bin/zsh #this causes the subsequent lines to not run.
source ~/.zshrc #this causes the error lines to be printed
# setopt aliases #don't think it helps
something #trying to invoke this which is already in zshrc
)
The error messages:
/Users/ytbryan/.zprezto/init.zsh: line 14: autoload: command not
found
/Users/ytbryan/.zprezto/init.zsh: line 15: print: command not
found
/Users/ytbryan/.zshrc: line 42: `#': not a valid identifier
One approach is to run Zsh code via zsh -c. Aliases are not expanded when run from zsh -c, but the builtin aliases array is still accessible, so one can still expand aliases by manually retrieving expansions from the array and manually performing word splitting. This should cover most of the commonly seen aliases. For more advanced aliases (that involves process substitution, parameter expansion, command substitution, arithmetic expansion, brace expansion, filename expansion or filename generation, or that is more than a simple command), one might need to use eval (but one needs to be very cautious when using eval, and never ever use it when input comes from an untrusted source, or from a trusted but possibly tempered-with source).
Example code that could be embedded in Ruby system calls:
> zsh -c 'alias foo="print bar"; ${=aliases[foo]}'
bar
> zsh -c 'alias foo=print; ${=aliases[foo]} $#' -- 1 2 3
1 2 3
> zsh -c 'alias foo="print a b c | grep -o a"; ${=aliases[foo]}' # simple case where naive expansion fails
a b c | grep -o a
> zsh -c 'alias foo="print a b c | grep -o a"; eval "$aliases[foo]"' # eval comes to rescue, but be extra careful
a
Note that source works in zsh -c, so the alias definitions above could be sourced from any file just fine.
Related
I am trying to dynamically create alias' from the output of another command line tool.
For example:
> MyScript
blender="/opt/apps/blender/blender/2.79/blender"
someOtherAlias="ls -l"
I am trying the following code:
MyScript | {
while IFS= read -r line;
do
`echo alias $line`;
done;
}
But when I run this, I get the following error:
bash: alias: -l": not found
Just trying to run this command by itself gives me the same error:
> `echo 'alias someOtherAlias="ls -l"'`
bash: alias: -l": not found
But obviously the following command does work:
alias someOtherAlias="ls -l"
I've tried to find someone else who may have done this before, but none of my searches have come up with anything.
I would appreciate any and all help. Thanks!
See how bash (and posix shells) command parsing and quoting works and see difference between syntax and literal argument: for example '.."..' "..'.." are litteral quotes in an argument whereas " or ' are shell syntax and are not part of argument
also, enabling tacing with set -x may help to understand :
set -x
`echo 'alias someOtherAlias="ls -l"'`
++ echo 'alias someOtherAlias="ls -l"'
+ alias 'someOtherAlias="ls' '-l"'
bash: alias: -l": not found
bash sees 3 words : alias, someOtherAlias="ls and -l".
and alias loops over its arguments if they contain a = it create an alias otherwise it displays what alias argument is as -l" is not an alias it shows the error.
Note also as backquotes means command is run in a subshell (can be seen with mutiple + in trace) it will have no effect in current shell.
eval may be use to reinterpret literal as bash syntax (or to parse again a string).
So following should work, but be careful using eval on arbitrary arguments (from user input) can run arbitrary command.
eval 'alias someOtherAlias="ls -l"'
Finally also as bash commands after pipe are also run in subshell.
while IFS= read -r line;
do
`echo alias $line`;
done <MyScript
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
Here is a simple test case script which behaves differently in zsh vs bash when I run with $ source test_script.sh from the command line. I don't necessarily know why there is a difference if my shebang clearly states that I want bash to run my script other than the fact that the which command is a built-in in zsh and a program in bash. (FYI - the shebang directory is where my bash program lives which may not be the same as yours--I installed a new version using homebrew)
#!/usr/local/bin/bash
if [ "$(which ls)" ]; then
echo "ls command found"
else
echo "ls command not found"
fi
if [ "$(which foo)" ]; then
echo "foo command found"
else
echo "foo command not found"
I run this script with source ./test-script.sh from zsh and Bash.
Output in zsh:
ls command found
foo command found
Output in bash:
ls command found
foo command not found
My understanding is that default for test or [ ] (which are the same thing) evaluate a string to true if it's not empty/null. To illustrate:
zsh:
$ which foo
foo not found
bash:
$ which foo
$
Moreover if I redirect standard error in zsh like:
$ which foo 2> /dev/null
foo not found
zsh still seems to send foo not found to standard output which is why (I am guessing) my test case passed for both under the zshell; because the expansion of "$(which xxx)" returned a string in both cases (e.g. /some/directory and foo not found (zsh will ALWAYS return a string?).
Lastly, if I remove the double quotes (e.g. $(which xxx)), zsh gives me an error. Here is the output:
ls command found
test_scritp.sh:27: condition expected not:
I am guessing zsh wanted me to use [ ! "$(which xxx)" ]. I don't understand why? It never gave that error when running in bash (and isn't this supposed to run in bash anyway?!).
Why isn't my script using bash? Why is something so trivial as this not working? I understand how to make it work fine in both using the -e option but I simply want to understand why this is all happening. Its driving me bonkers.
There are two separate problems here.
First, the proper command to use is type, not which. Like you note, the command which is a zsh built-in, whereas in Bash, it will execute whatever which command happens to be on your system. There are many variants with different behaviors, which is why POSIX opted to introduce a replacement instead of trying to prescribe a particular behavior for which -- then there would be yet one more possible behavior, and no way to easily root out all the other legacy behaviors. (One early common problem was with a which command which would examine the csh environment, even if you actually used a different shell.)
Secondly, examining a command's string output is a serious antipattern, because strings differ between locales ("not found" vs. "nicht gefunden" vs. "ei löytynyt" vs. etc etc) and program versions -- the proper solution is to examine the command's exit code.
if type ls >/dev/null 2>&1; then
echo "ls command found"
else
echo "ls command not found"
fi
if type foo >/dev/null 2>&1; then
echo "foo command found"
else
echo "foo command not found"
fi
(A related antipattern is to examine $? explicitly. There is very rarely any need to do this, as it is done naturally and transparently by the shell's flow control statements, like if and while.)
Regarding quoting, the shell performs whitespace tokenization and wildcard expansion on unquoted values, so if $string is command not found, the expression
[ $string ]
without quotes around the value evaluates to
[ command not found ]
which looks to the shell like the string "command" followed by some cruft which isn't syntactically valid.
Lastly, as we uncovered in the chat session (linked from comments) the OP was confused about the precise meaning of source, and ended up running a Bash script in a separate process instead. (./test-script instead of source ./test-script). For the record, when you source a file, you cause your current shell to read and execute it; in this setting, the script's shebang line is simply a comment, and is completely ignored by the shell.
This command works fine:
$ bash -s stable < <(curl -s https://raw.github.com/wayneeseguin/rvm/master/binscripts/rvm-installer)
However, I don't understand how exactly stable is passed as a parameter to the shell script that is downloaded by curl. That's the reason why I fail to achieve the same functionality from within my own shell script - it gives me ./foo.sh: 2: Syntax error: redirection unexpected:
$ cat foo.sh
#!/bin/sh
bash -s stable < <(curl -s https://raw.github.com/wayneeseguin/rvm/master/binscripts/rvm-installer)
So, the questions are: how exactly this stable param gets to the script, why are there two redirects in this command, and how do I change this command to make it work inside my script?
Regarding the "redirection unexpected" error:
That's not related to stable, it's related to your script using /bin/sh, not bash. The <() syntax is unavailable in POSIX shells, which includes bash when invoked as /bin/sh (in which case it turns off nonstandard functionality for compatibility reasons).
Make your shebang line #!/bin/bash.
Understanding the < <() idiom:
To be clear about what's going on -- <() is replaced with a filename which refers to the output of the command which it runs; on Linux, this is typically a /dev/fd/## type filename. Running < <(command), then, is taking that file and directing it to your stdin... which is pretty close the behavior of a pipe.
To understand why this idiom is useful, compare this:
read foo < <(echo "bar")
echo "$foo"
to this:
echo "bar" | read foo
echo "$foo"
The former works, because the read is executed by the same shell that later echoes the result. The latter does not, because the read is run in a subshell that was created just to set up the pipeline and then destroyed, so the variable is no longer present for the subsequent echo.
Understanding bash -s stable:
bash -s indicates that the script to run will come in on stdin. All arguments, then, are fed to the script in the $# array ($1, $2, etc), so stable becomes $1 when the script fed in on stdin is run.
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=