bash shell script alias expansion not working inside if statement [duplicate] - linux

I can't get expand_aliases to take effect in bash. I've tried a lot of different things, and nothing works.
Here's the simple test case:
/bin/bash -c 'shopt -s expand_aliases; alias cdtmp="cd /tmp"; alias; cdtmp; pwd;'
And the output:
$ /bin/bash -c 'shopt -s expand_aliases; alias cdtmp="cd /tmp"; alias; cdtmp; pwd;'
alias cdtmp='cd /tmp'
/bin/bash: cdtmp: command not found
/home/user
$ /bin/bash --version
GNU bash, version 3.2.25(1)-release (i686-redhat-linux-gnu)
Copyright (C) 2005 Free Software Foundation, Inc.
(Yes, I'm using shopt instead of the -O option to bash, just to prove it's being done.)
Any ideas?

Aliases aren't available on the same line or in the same function where they are defined.
From the Bash man page:
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 com‐
pound 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 com‐
pound commands.
For almost every purpose, aliases are superseded by shell functions.
The Bash Reference Manual says
For almost every purpose, shell functions are preferred over aliases.
instead of the last sentence above [emphasis mine]. I consider aliases to be a command-line convenience rather than something that should be used in scripts (including those which consist solely of bash -c one-liners).

I came late for 10 years.
you can use the eval to make the alias cdtmp expand again.
/bin/bash -c 'shopt -s expand_aliases; alias cdtmp="cd /tmp"; alias; eval cdtmp; pwd;'
will give you output
alias cdtmp='cd /tmp'
/tmp

try /bin/bash -O expand_aliases -c 'xx'

Related

Need an elegant way to overload a command in a Bash script dynamically created and called by another tool

There have been a lot of posts about how to overload commands in Bash, either by creating an alias or function, or just changing the script itself or how it is called; these methods make perfect sense to me but don't apply to the problem I'm trying to solve.
It is common in the semiconductor industry to work with EDA tools that provide API commands inside a Tcl interpreter; in my case the EDA tool generates a Bash script, then executes it one API call, so I only have access to the script after the script executed and cannot modify how it is called since that happens under the hood; there is also no blessed method for modifying the commands of interest that are written into the script.
What complicates this further is that this script is generated and called by the EDA tool inside a Singularity container; also, the command that is being called resides in a different EDA tool container, so this command 'vsim' must be transformed into an ssh call to our workload manager (slurm). Example:
vsim <args>
Must be converted to:
ssh <some-host> <ssh-options> "srun <srun-options> vsim <args>"
Again, setting an alias or function called 'vsim' in the parent shell didn't work for me; the alias/function was not inherited by the subprocess (this is difficult for me to accept). I also tried creating a symlink called 'vlog' that pointed at a static dispatcher script; this worked by I'd rather not hard code the path to the dispatcher as a file path; I would rather use a variable, like vlog -> .../<resource>/$VERSION/bin/dispatch_for_flows, but symlinks are not dynamic.
I'm thinking that I should still use the symlink approach but instead of pointing at the dispatcher, I'd point at a wrapper that does a single line exec of an interpolated path to the dispatcher, but I'm still not believing that setting an alias didn't solve the problem. For years I wrote Csh scripts with full paths to commands to get around user aliases; with Bash, it looks like aliases are not inherited ever, even with the shopt 'expanand_alias' on in the parent shell or even in /etc/profile. What am I missing here?
Can someone get the example below to work? I'd like the script to inherit the alias from the parent shell and execute without the "command not found" error, without modification to the script or how it is called. Again, I have no access to the script before it is executed and can't control how it is called; bash is the parent shell, bash is the script, and I do have root access on the OS so I could change the version of bash if neccessary (using 4.4.19(1)-release (x86_64-redhat-linux-gnu))
$ alias hello='echo world'
$ hello
world
$ shopt | grep expand_aliases
expand_aliases on
$ cat test.bash
#!/usr/bin/bash
set -x
alias
shopt | grep expand_aliases
shopt -s expand_aliases
shopt | grep expand_aliases
hello
$ ./test.bash
+ alias
+ shopt
+ grep expand_aliases
expand_aliases off
+ shopt -s expand_aliases
+ shopt
+ grep expand_aliases
expand_aliases on
+ hello
./test.bash: line 7: hello: command not found
Can someone get the example below to work?
No that's not possible with aliases - you can't export an alias. But you can export a function.
$ hello() { echo world; }
$ export -f hello
$ ./test.bash
world
You can create a custom command in PATH:
$ printf "%s\n" "#!/bin/bash" "echo world" > /usr/local/bin/hello
$ chmod +x /usr/local/bin/hello
and similarly add a custom command and add new path with the command to PATH and export.

What does the -b argument do in csh?

I am looking at a tcsh script that has the following shebang line:
#!/bin/tcsh -fb
# then executes some commands
What does the -b do?
From the man page:
-b Forces a ''break'' from option processing, causing any further shell arguments to
be treated as non-option arguments. The remaining arguments will not be inter-
preted as shell options. This may be used to pass options to a shell script with-
out confusion or possible subterfuge. The shell will not run a set-user ID script
without this option.
But I don't really understand what it means...
An example would be great.
Thanks.
Say, for example, you have a script that is named --help and you want to execute it using tcsh:
tcsh --help
This will obviously not work. The -b forces tcsh to stop looking for arguments and treat the rest of the command line as file names or arguments to scripts. So, to run the above weirdly named script, you could do
tcsh -b --help

alias in a script

In linux, if I put a command in a script that contains an alias, I see that it doesn't get expanded. How can I fix that? I am using bash.
According the the TLDP page about aliases, you need to use the line shopt -s expand_aliases in your code to expand aliases. The example below produced the expected output, but without the shopt line it just printed "my_ls: command not found":
#!/bin/bash
shopt -s expand_aliases
alias my_ls='ls -lrt'
echo "Trying my_ls"
my_ls
exit
If you want your shell aliases to be available in a script, you must manually include them. If they are defined in ~/.bashrc, put a line
. ~/.bashrc
after the #!/bin/sh line in your script. This will execute the contents of .bashrc in the context of your script.
Enabling posix mode (such as by calling bash as sh or with the command (set -o posix) 2>/dev/null && set -o posix should do the trick.
Even then, be aware that aliases are expanded as they are parsed, and the ordering between parsing and execution is poorly defined. For example
alias foo=echo; foo bar
or
{
alias foo=echo
foo bar
}
will try to run foo as the alias is not defined at parse time yet. Also, some shells parse the whole input of an eval or . (source) before executing any of it.
So the only portable and reliable way to use aliases in scripts is to define them and then eval or . the code that uses them.

what are shell built-in commands in linux?

I have just started using Linux and I am curious how shell built-in commands such as cd are defined.
Also, I'd appreciate if someone could explain how they are implemented and executed.
If you want to see how bash builtins are defined then you just need to look at Section 4 of The Bash Man Page.
If, however, you want to know how bash bultins are implemented, you'll need to look at the Bash source code because these commands are compiled into the bash executable.
One fast and easy way to see whether or not a command is a bash builtin is to use the help command. Example, help cd will show you how the bash builtin of 'cd' is defined. Similarly for help echo.
The actual set of built-ins varies from shell to shell. There are:
Special built-in utilities, which must be built-in, because they have some special properties
Regular built-in utilities, which are almost always built-in, because of the performance or other considerations
Any standard utility can be also built-in if a shell implementer wishes.
You can find out whether the utility is built in using the type command, which is supported by most shells (although its output is not standardized). An example from dash:
$ type ls
ls is /bin/ls
$ type cd
cd is a shell builtin
$ type exit
exit is a special shell builtin
Re cd utility, theoretically there's nothing preventing a shell implementer to implement it as external command. cd cannot change the shell's current directory directly, but, for instance, cd could communicate new directory to the shell process via a socket. But nobody does so because there's no point. Except very old shells (where there was not a notion of built-ins), where cd used some dirty system hack to do its job.
How is cd implemented inside the shell? The basic algorithm is described here. It can also do some work to support shell's extra features.
Manjari,
Check the source code of bash shell from ftp://ftp.gnu.org/gnu/bash/bash-2.05b.tar.gz
You will find that the definition of shell built-in commands in not in a separate binary executable but its within the shell binary itself (the name shell built-in clearly suggests this).
Every Unix shell has at least some builtin commands. These builtin commands are part of the shell, and are implemented as part of the shell's source code. The shell recognizes that the command that it was asked to execute was one of its builtins, and it performs that action on its own, without calling out to a separate executable. Different shells have different builtins, though there will be a whole lot of overlap in the basic set.
Sometimes, builtins are builtin for performance reasons. In this case, there's often also a version of that command in $PATH (possibly with a different feature set, different set of recognized command line arguments, etc), but the shell decided to implement the command as a builtin as well so that it could save the work of spawning off a short-lived process to do some work that it could do itself. That's the case for bash and printf, for example:
$ type printf
printf is a shell builtin
$ which printf
/usr/bin/printf
$ printf
printf: usage: printf [-v var] format [arguments]
$ /usr/bin/printf
/usr/bin/printf: missing operand
Try `/usr/bin/printf --help' for more information.
Note that in the above example, printf is both a shell builtin (implemented as part of bash itself), as well as an external command (located at /usr/bin/printf). Note that they behave differently as well - when called with no arguments, the builtin version and the command version print different error messages. Note also the -v var option (store the results of this printf into a shell variable named var) can only be done as part of the shell - subprocesses like /usr/bin/printf have no access to the variables of the shell that executed them.
And that brings us to the 2nd part of the story: some commands are builtin because they need to be. Some commands, like chmod, are thin wrappers around system calls. When you run /bin/chmod 777 foo, the shell forks, execs /bin/chmod (passing "777" and "foo") as arguments, and the new chmod process runs the C code chmod("foo", 777); and then returns control to the shell. This wouldn't work for the cd command, though. Even though cd looks like the same case as chmod, it has to behave differently: if the shell spawned another process to execute the chdir system call, it would change the directory only for that newly spawned process, not the shell. Then, when the process returned, the shell would be left sitting in the same directory as it had been in all along - therefore cd needs to be implemented as a shell builtin.
A Shell builtin -- http://linux.about.com/library/cmd/blcmdl1_builtin.htm
for eg. -
which cd
/usr/bin/which: no cd in (/usr/bin:/usr/local/bin......
Not a shell builtin but a binary.
which ls
/bin/ls
http://ss64.com/bash/ this will help you.
and here is shell scripting guide
http://www.freeos.com/guides/lsst/

How can I define a bash alias as a sequence of multiple commands? [duplicate]

This question already has answers here:
Multiple commands in an alias for bash
(10 answers)
Closed 4 years ago.
I know how to configure aliases in bash, but is there a way to configure an alias for a sequence of commands?
I.e say I want one command to change to a particular directory, then run another command.
In addition, is there a way to setup a command that runs "sudo mycommand", then enters the password? In the MS-DOS days I'd be looking for a .bat file but I'm unsure of the linux (or in this case Mac OSX) equivalent.
For chaining a sequence of commands, try this:
alias x='command1;command2;command3;'
Or you can do this:
alias x='command1 && command2 && command3'
The && makes it only execute subsequent commands if the previous returns successful.
Also for entering passwords interactively, or interfacing with other programs like that, check out expect. (http://expect.nist.gov/)
You mention BAT files so perhaps what you want is to write a shell script. If so then just enter the commands you want line-by-line into a file like so:
command1
command2
and ask bash to execute the file:
bash myscript.sh
If you want to be able to invoke the script directly without typing "bash" then add the following line as the first line of the file:
#! /bin/bash
command1
command2
Then mark the file as executable:
chmod 755 myscript.sh
Now you can run it just like any other executable:
./myscript.sh
Note that unix doesn't really care about file extensions. You can simply name the file "myscript" without the ".sh" extension if you like. It's that special first line that is important. For example, if you want to write your script in the Perl programming language instead of bash the first line would be:
#! /usr/bin/perl
That first line tells your shell what interpreter to invoke to execute your script.
Also, if you now copy your script into one of the directories listed in the $PATH environment variable then you can call it from anywhere by simply typing its file name:
myscript.sh
Even tab-completion works. Which is why I usually include a ~/bin directory in my $PATH so that I can easily install personal scripts. And best of all, once you have a bunch of personal scripts that you are used to having you can easily port them to any new unix machine by copying your personal ~/bin directory.
it's probably easier to define functions for these types of things than aliases, keeps things more readable if you want to do more than a command or two:
In your .bashrc
perform_my_command() {
pushd /some_dir
my_command "$#"
popd
}
Then on the command line you can simply do:
perform_my_command my_parameter my_other_parameter "my quoted parameter"
You could do anything you like in a function, call other functions, etc.
You may want to have a look at the Advanced Bash Scripting Guide for in depth knowledge.
For the alias you can use this:
alias sequence='command1 -args; command2 -args;'
or if the second command must be executed only if the first one succeeds use:
alias sequence='command1 -args && command2 -args'
Your best bet is probably a shell function instead of an alias if the logic becomes more complex or if you need to add parameters (though bash supports aliases parameters).
This function can be defined in your .profile or .bashrc. The subshell is to avoid changing your working directory.
function myfunc {
( cd /tmp; command )
}
then from your command prompt
$ myfunc
For your second question you can just add your command to /etc/sudoers (if you are completely sure of what you are doing)
myuser ALL = NOPASSWD: \
/bin/mycommand
Apropos multiple commands in a single alias, you can use one of the logical operators to combine them. Here's one to switch to a directory and do an ls on it
alias x="cd /tmp && ls -al"
Another option is to use a shell function. These are sh/zsh/bash commands. I don't know enough of other shells to be sure if they work.
As for the sudo thing, if you want that (although I don't think it's a good idea), the right way to go is to alter the /etc/sudoers file to get what you want.
You can embed the function declaration followed by the function in the alias itself, like so:
alias my_alias='f() { do_stuff_with "$#" (arguments)" ...; }; f'
The benefit of this approach over just declaring the function by itself is that you can have a peace of mind that your function is not going to be overriden by some other script you're sourcing (or using .), which might use its own helper under the same name.
E.g., Suppose you have a script init-my-workspace.sh that you're calling like . init-my-workspace.sh or source init-my-workspace.sh whose purpose is to set or export a bunch of environment variables (e.g., JAVA_HOME, PYTHON_PATH etc.). If you happen to have a function my_alias inside there, as well, then you're out of luck as the latest function declaration withing the same shell instance wins.
Conversely, aliases have separate namespace and even in case of name clash, they are looked up first. Therefore, for customization relevant to interactive usage, you should only ever use aliases.
Finally, note that the practice of putting all the aliases in the same place (e.g., ~/.bash_aliases) enables you to easily spot any name clashes.
you can also write a shell function; example for " cd " and "ls " combo here

Resources