bash: unable to set and use alias in the same line - bash

I would expect the second line to say foo instead of command not found:
$ alias foo="echo bac" ; foo;
-bash: foo: command not found
$ foo
bac
$
Why won't the second line say foo? Tested with the following shells, same behavior:
bash 3.2.5
zsh 5.0.8
dash 0.5.9
busybox 1.25.0

The behaviour you're seeing is described in the Bash Reference Manual (emphasis mine):
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.
Presumably the other shells also behave in this way.

To set and use alias in the same line in bash, you can use:
eval $'alias df5=df\ndf5 -h'
(credits: Hauke Laging's workaround + Kusalananda's workaround).
Explanation of the command:
Since "an alias definition appearing on the same line as another command does not take effect until the next line of input is read" according to the bash manual as Tom Fenech's pointed out, we use eval and place a new line between the alias definition and its use.
From Kusalananda's workaround:
"The $'...' is a "C string", and bash would expand the \n within it to a literal newline before passing it to eval.

Related

How do I call an alias that is defined within a function that is chained before the alias calling? [duplicate]

I would expect the second line to say foo instead of command not found:
$ alias foo="echo bac" ; foo;
-bash: foo: command not found
$ foo
bac
$
Why won't the second line say foo? Tested with the following shells, same behavior:
bash 3.2.5
zsh 5.0.8
dash 0.5.9
busybox 1.25.0
The behaviour you're seeing is described in the Bash Reference Manual (emphasis mine):
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.
Presumably the other shells also behave in this way.
To set and use alias in the same line in bash, you can use:
eval $'alias df5=df\ndf5 -h'
(credits: Hauke Laging's workaround + Kusalananda's workaround).
Explanation of the command:
Since "an alias definition appearing on the same line as another command does not take effect until the next line of input is read" according to the bash manual as Tom Fenech's pointed out, we use eval and place a new line between the alias definition and its use.
From Kusalananda's workaround:
"The $'...' is a "C string", and bash would expand the \n within it to a literal newline before passing it to eval.

Difference between a semicolon and a new line in bash ("source rcfile; foo" vs. "source rcfile<NEWLINE>foo" where "foo" is an alias defined in rcfile)

I thought that giving a semicolon in bash is the same as giving a new line, just combining multiple lines.
However, with alias sudo='sudo -i' in the .bashrc file, the below result doesn't seem to work as I expected:
USER#HOST:~:$ cat tmp-1.sh
#!/bin/bash
source ~/.bashrc; sudo env | grep PATH
USER#HOST:~:$ cat tmp-2.sh
#!/bin/bash
source ~/.bashrc
sudo env | grep PATH
USER#HOST:~:$ ./tmp-1.sh
PATH=/usr/bin:/bin:/usr/sbin:/sbin
USER#HOST:~:$ ./tmp-2.sh
PATH=/home1/client/bin:/usr/python-3.8.2-r2/bin:/usr/jdk64/jdk1.8.0_112/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/bin:/bin:/usr/sbin:/sbin:/usr/bin:/root/bin
What's the difference between these two?
As in your comment:
alias sudo='sudo -i' is in .bashrc file
According to man bash:
The rules concerning the definition and use of aliases are somewhat confusing. Bash always reads at
least one complete line of input, and all lines that make up a compound command, before executing any
of the commands on that line or the compound command. 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.
For almost every purpose, aliases are superseded by shell functions.

Does bash think this argument is a command? [duplicate]

I am writing a shell script where I am setting few variables, whose value is the output of commands.
The errors I get are:
$ $tag_name="proddeploy-$(date +"%Y%m%d_%H%M")"
-bash: =proddeploy-20141003_0500: command not found
now, I did read other similar questions and based on it, I tried various things:
spliting command into two calls
$ $deploy_date=date +"%Y%m%d_%H%M"
bash: =date: command not found
$ $tag_name="proddeploy-$deploy_date"
bash: proddeploy- command not found
tried using backticks
$ $tag_name=`proddeploy-$(date +"%Y%m%d_%H%M")`
bash: proddeploy-20141003_1734: command not found
bash: =: command not found
tried using $()
$ $tag_name=$(proddeploy-$(date +"%Y%m%d_%H%M"))
bash: proddeploy-20141003_1735: command not found
bash: =: command not found
But in every case the command output is getting executed. how do I make it to stop executing command output and just store as a variable? I need this to work on ZSH and BASH.
You define variables with var=string or var=$(command).
So you have to remove the leading $ and any other signs around =:
tag_name="proddeploy-$(date +"%Y%m%d_%H%M")"
deploy_date=$(date +"%Y%m%d_%H%M")
^^ ^
From Command substitution:
The second form `COMMAND` is more or less obsolete for Bash, since it
has some trouble with nesting ("inner" backticks need to be escaped)
and escaping characters. Use $(COMMAND), it's also POSIX!
Also, $() allows you to nest, which may be handy.
The accepted answer shows corrected code, but does not clarify that one of your problems is accessing a variable (using $) while assigning it, which is illegal.
For example:
$foo=4
should be
foo=4
See the difference? foo is being assigned, so you should not use $foo, which is not foo but the value of foo.

Why do csh(tcsh) aliases not work when defined and invoked on one line?

[Please do not recommend I not use csh. The Electrical Engineering community is inextricably bound to it. Thanks!]
When I do this:
csh -f -c "alias foo bar; foo"
I get:
foo: Command not found.
Similarly, when I do this:
#!/bin/csh -f
alias foo bar; foo
I get the same foo: Command not found.. However, this works as expected:
#!/bin/csh -f
alias foo bar
foo
Which gives:
bar: Command not found.
Is this just a bug in csh/tcsh? Or is this intentional? I have to deal with aliases since the environment I'm using depends heavily on them to configure toolsets (including modules). But this basically means I can't invoke short csh scripts with csh -f -c. I have to dump the commands to a file, chmod +x, and invoke it. Not a huge deal. But I'm just wondering if there's a way to trick this buggy/quirky shell into recognizing aliases defined on the same line.
This is documented behavior of csh: alias substitution happens after each input line is read and split into commands, but before any of the commands are executed (including your alias command).
From csh(1) manual:
Alias substitution
The shell maintains a list of aliases that can be established, displayed
and modified by the alias and unalias commands. After a command line is scanned, it is parsed into distinct commands and the first word of each
command, left-to-right, is checked to see if it has an alias. If it
does, then the text that is the alias for that command is reread with the
history mechanism available as though that command were the previous
input line. The resulting words replace the command and argument list.
If no reference is made to the history list, then the argument list is
left unchanged.

Bash: declaration of an alias to be used in a single lined multiple commands

I would like to do something like
alias myls=ls && myls
This gives me an error
bash: myls: command not found
Strangely, a subsequent
myls
in the same session would be recognized.
Do you know how to declare alias, that is used in the same command sequence?
According to the bash manual:
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 compound 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.
For almost every purpose, aliases are superseded by shell functions.
alias has to be used on a separate line so even this won't work:
alias myls=ls; myls
This will result in -bash: myls: command not found error.
However, you can use function and use it on same line:
myls() { ls "$#"; } && myls
anubhava has already provided a better answer, but if for some reason you don't want to use a function instead of an alias then you can do
alias myls=ls && eval myls
Note, however, that usage of eval introduces an extra level of expansion, which may result in unexpected behaviour for more complex commands:
$ echo "A B"
A B
$ eval echo "A B"
A B
$ echo '$SHELL'
$SHELL
$ eval echo '$SHELL'
/bin/bash

Resources