Prevent bash alias from evaluating statement at shell start - bash

Say I have the following alias.
alias pwd_alias='echo `pwd`'
This alias is not "dynamic". It evaluates pwd as soon as the shell starts. Is there anyway to delay the evaluation of the expression in the ticks until the alias's runtime?

What you really want is a function, instead of an alias.
pwd_alias() {
echo "$PWD"
}
Aliases do nothing more than replace text. Anything with complexity calls for a function.

As jordanm said, aliases do nothing more than replace text.
If you want the argument of echo to be the output of pwd expanded by bash, then I don't understand your question.
If you want the argument of echo to be `pwd` with the backquotes kept, it's indeed possible, for example:
alias a="echo '\`pwd\`'"
So, if instead of echo you have something which does backquote expansion in its own runtime, maybe that's what you want.

I do not believe you can change the evaluation from occurring at shell start. Since the processes of creating the alias is run at shell start the pwd is evaluated then. You could simple change the alias to just run pwd without the back ticks as pwd outputs without the need to echo. A simple way to resolve this is to change from using an alias to a shell script in your path if you do not wish to change from using an alias.
#!/bin/bash
pwd

Related

Bash script ignores positional arguments after first time used

I noticed that my script was ignoring my positional arguments in old terminal tabs, but working on recently created ones, so I decided to reduce it to the following:
TAG=test
while getopts 't:' c
do
case $c in
t)
TAG=$OPTARG
;;
esac
done
echo $TAG
And running the script I have:
~ source my_script
test
~ source my_script -t "test2"
test2
~ source my_script -t "test2"
test
I thought it could be that c was an special used variable elsewhere but after changing it to other names I had the exact same problem. I also tried adding a .sh extension to the file to see it that was a problem, but nothing worked.
Am I doing something wrong ? And why does it work the first time, but not the subsequent attempts ?
I am on MacOS and I use zsh.
Thank you very much.
The problem is that you're using source to run the script (the . command does the same thing). This makes it run in your current (interactive) shell (rather than a subprocess, like scripts normally do). This means it uses the same variables as the current shell, which is necessary if you want it to change those variables, but it can also have weird effects if you're not careful.
In this case, the problem is that getopts uses the variable OPTIND to keep track of where it is in the argument list (so it doesn't process the same argument twice). The first time you run the script with -t test2, getopts processes those arguments, and leaves OPTIND set to 3 (meaning that it's already done the first two arguments, "-t" and "test2". The second time you run it with options, it sees that OPTIND is set to 3, so it thinks it's already processed both arguments and just exits the loop.
One option is to add unset OPTIND before the while getopts loop, to reset the count and make it start from the beginning each time.
But unless there's some reason for this script to run in the current shell, it'd be better to make it a standard shell script and have it run as a subprocess. To do this:
Add a "shebang" line as the first line of the script. To make the script run in bash, that'd be either #!/bin/bash or #!/usr/bin/env bash. For zsh, use #!/bin/zsh or #!/usr/bin/env zsh. Since the script runs in a separate shell process, the you can run bash scripts from zsh or zsh scripts from bash, or whatever.
Add execute permission to the script file with chmod -x my_script (or whatever the file's actual name is).
Run the script with ./my_script (note the lack of a space between . and /), or by giving the full path to the script, or by putting the script in some directory in your PATH (the directories that're automatically searched for commands) and just running my_script. Do NOT run it with the bash, sh, zsh etc commands; these override the shebang and therefore can cause confusion.
Note: adding ".sh" to the filename is not recommended; it does nothing useful, and makes the script less convenient to run since you have to type in the extension every time you run it.
Also, a couple of recommendations: there are a bunch of all-caps variable names with special meanings (like PATH and OPTIND), so unless you want one of those special meanings, it's best to use lower- or mixed-case variable names (e.g. tag instead of TAG). Also, double-quoting variable references (e.g. echo "$tag" instead of echo $tag) avoids a lot of weird parsing headaches. Run your scripts through shellcheck.net; it's good at spotting common mistakes like this.

How to force interpretation as keyword instead of alias on command line?

I have
alias time='/usr/bin/time -v'
configured in my bash environment.
However, I sometimes need an access to keyword time for some one-liners such as
(expensive-command1 & expensive-command2 & time wait); something-that-requires-prev-commands-to-be-complete
Where the wait should obviously wait for both expensive-command1 and expensive-command2 to complete and the keyword time is able to measure the time taken.
However, when I have alias time pointing to actual binary I get following error instead:
/usr/bin/time: cannot run wait: No such file or directory
How to force interpretation keyword for the time in command above? I know that if I run unalias time before running above line it works but then my alias is gone for that shell.
I also know that if I wanted to go from keyword to actual binary, I could use syntax (cmd1 & cmd2 & command time wait). And if I needed built-in command I could use syntax (cmd1 & cmd2 & builtin time wait). However, the time needed here is not command nor builtin. It's a keyword but I cannot figure out how to get the above one-liner to be interpreted as such.
Define a function like below before the line where alias time is defined in your bashrc.
_time() { time "$#"; }
Then you can use _time whenever you need the keyword time.
$ _time() { time "$#"; }
$ alias time='echo foo'
$ time ls
foo ls
$ _time ls
.bash_history .bashrc
real 0m0.024s
user 0m0.010s
sys 0m0.000s
The manual explains why this works as follows.
Aliases are expanded when a function definition is read, not when the function is executed, because a function definition is itself a command.
In an environment where you don't have permission to change bashrc, you can do:
$ shopt -u expand_aliases
$ _time() { time "$#"; }
$ shopt -s expand_aliases
and get the same functionality.
As of Bash version 4.4, there's no nice way to force interpretation as keyword instead of alias. For workarounds, see answer by oguz ismail. In short, all workarounds create alternate name for the original keyword behavior and then you have to always use the alternate instead of the correct keyword.
For now, it seems that the best way to proceed is to consider all bash keywords as reserved words and never ever define any alias with identical name. This is the solution that I'm personally using.

'which' command is incorrect

I have a shell script in my home directory called "echo". I added my home directory to my path, so that this echo would replace the other one.
To do this, I used: export PATH=/home/me:$PATH
When I do which echo, it shows the one I want. /home/me/echo
But when I actually do something like echo asdf it uses the system echo.
Am I doing something wrong?
which is an external command, so it doesn't have access to your current shell's built-in commands, functions, or aliases. In fact, at least on my system, /usr/bin/which is a shell script, so you can examine it and see how it works.
If you want to know how your shell will interpret a command, use type rather than which. If you're using bash, type -a will print all possible meanings in order of precedence. Consult your shell's documentation for details.
For most shells, built-in commands take precedence over commands in your $PATH. The whole point of having a built-in echo, for example, is that it's faster than loading /bin/echo into memory.
If you want your own echo command to override the shell's built-in echo, you can define it as a shell function.
On the other hand, overriding the built-in echo command doesn't strike me as a good idea in the first place. If it behaves the same as the built-in echo, there's not much point. If it doesn't, then it could break scripts that use echo expecting it to work a certain way. If possible, I suggest giving your command a different way. If it's an enhanced version of echo, you could even call it Echo.
It is likely using the shell's builtin.
If you want the one in your path you can do
`which echo` asdf
From this little article that explains the rules, here's a list in descending order of precedence:
Aliases
Shell functions
Shell builtin commands
Hash tables
PATH variable
echo is a shell builtin command (al least in bash) and PATH has the lowest priority. I guess you'll need to create a function or an alias.

TCSH, what does this line do? Trying to port to BASH

I'm trying to update some scripts from tcsh to bash to reflect the bash preference of some users. Needless to say, I don't know csh. Can someone tell me what this line does?
alias prepend 'if (-d \!:2) if ("$\!:1" \!~ *"\!:2"*) export \!:1 "\!:2":${\!:1}'
That probably prepends a directory onto a variable if said directory doesn't already exist in said variable.
Here's what it says, in English: if the second argument is a directory, then if the first argument interpreted as a variable does not contain the text of the second argument, then "export" the string "second argument colon contents of first argument" into the first argument.
It all depends upon what export does. In my experience, export is an alias for setenv, but that is not guaranteed.
I would suggest a test to see if this does what I think. First, echo your path. Then run prepend PATH /a/new/directory/that/exists. Then echo your path again. If you see that "/a/new/directory/that/exists" is now in your path variable, then you can be reasonably sure export is an alias for setenv.
Finally, this SO post lists strategies to implement a similar thing in other languages and shells.

Problem in running a script

i have unix shell script which is need to be run like below
test_sh XYZ=KLMN
the content of the script is
#!/bin/ksh
echo $XYZ
for using the value of XYZ i have do set -k before i run the script.
is there a way where i can do this without doint set -k before running the script. or is there something that i can do in the script where i can use value of the parameter given while running the script in the below way
test_sh XYZ=KLMN
i am using ksh.
Any help is appreciated.
How about running this?
XYZ=KLMN ./test_sh //running from directory where test_sh is
If your script needs no other arguments, a quick and dirty way do to it is to put
eval "$#"
at the start of your script. This will evaluate the command line arguments as shell commands. If those commands are to assign a shell/environment variable, then that's what it will do.
It's quick-and-dirty since anything could be put on the command line, causing problems from a syntax error to a bad security hole (if the script is trusted).
I'm not sure if "$#" means the same in ksh as it does in bash - using just $* (without quotes) would work too, but is even dirtier.
It looks like you are trying to use the environment variable "INSTANCE" in your script.
For that, the environment variable must be set in advance of executing your script. Using the "set" command sets exportable environment variables. Incidentally, my version of ksh dates from 1993 and the "-k" option was obsolete back then.
To set an environment variable so that it is exported into spawned shells, simply use the "export" command like so:
export INSTANCE='whatever you want to put here'
If you want to use a positional parameter for your script -- that is have the "KLMN" value accessed within your script, and assuming it is the first parameter, then you do the following in your script:
#!/bin/ksh
echo $1
You can also assign the positional parameter to a local variable for later use in your script like so:
#!/bin/ksh
param_one=$1
echo $param_one
You can call this with:
test_sh KLMN
Note that the spacing in the assignment is important -- do not use spaces.
I am tring this option
#!/bin/ksh
echo $1
awk '{FS="=";print $2}' $1
and on the command line
test_sh INSTANCE=LSN_MUM
but awk is failing.is there any problem over here?
Probably #!/bin/ksh -k will work (untested).

Resources