Bash alias using !$ - bash

I found out today that I can write !$ to get the last argument from the last command executed.
Now I'm trying to create an alias using that shortcut and it isn't working at all.
These are the ones I'm trying to create.
alias gal='git add !$'
alias gcl='git checkout !$'
alias sl='sublime !$'
And this is the result output when calling gal or gcl
fatal: pathspec '!$' did not match any files
So it seems like !$ just isn't being replaced by the last argument from the last command in this context.
Is it possible?

Instead of fiddling with Bash's history, you might as well want to use Bash's $_ internal variable: The relevant part of the manual states:
$_: […] expands to the last argument to the previous command, after expansion. […]
For example:
$ touch one two three
$ echo "$_"
three
$ ls
$ echo "$_"
ls
$ a='hello world'
$ echo $a
hello world
$ echo "$_"
world
$ echo "$a"
hello world
$ echo "$_"
hello world
$
In your case, your aliases would look like:
alias gal='git add "$_"'
alias gcl='git checkout "$_"'
alias sl='sublime "$_"'

You can use the bash builtin history command fc: an example
$ alias re_echo='echo $(fc -ln -2 | awk '\''NR==1 {print $NF}'\'')'
$ echo foo
foo
$ re_echo bar
foo bar
$ re_echo baz
bar baz
$ re_echo qux
baz qux

Related

Recall all arguments from a previously executed command in bash shell

This link gives the pointer on how to recall the arguments of the last successfully executed command in the command line.
I wanted to access all the arguments of a particular command from the shell history.
To me only this syntax works
ls f1 f2 f3
file !ls:1-3 --> file f1 f2 f3
And, if I use !* which should give all the arguments of previous command throws error
file !ls:!*
-bash: !: unrecognized history modifier
I can only use this syntax (i.e) all arguments of last executed command.
file !*
Problem with the above methods are if I had executed ls with some option, for eg: ls -l, then the file command would have thrown a different output as the option of ls would be considered as first argument in this case.
Try leaving out the second !:
$ ls foo bar baz
foo bar baz
$ echo !ls:*
echo foo bar baz
foo bar baz
!ls returns the last ls command. If you want the second to last or even an older ls execution you can use history to retrieve the command you want
history | grep ls
355 ls foo bar baz
446 ls -a
447 ls -ah
And then use #Jon solution or yours to get the arguemnts
echo !355:*

Why do alias foo1="echo $1" and alias foo2="echo '$1'" behave as they do?

In my .bashrc file, I put the following two lines:
alias foo1="echo $1"
alias foo2="echo '$1'"
Then, in the terminal, I get the following output:
$ foo1 hello world
hello world
$ foo2 hello world
hello world
Why is the extra space produced by foo2?
If I just do the following in terminal, the outputs are as shown:
$ echo hello world
hello world
$ echo 'hello world'
hello world
This leads me to think that foo1 and foo2 should do exactly the same thing. How come they do not actually output exactly the same thing, and why do they differ by simply one space character?
(Also, why do either of them output world? I would expect only the first argument to be outputted.)
If, in a typical interactive shell, you run:
$ set -- # this clears the argument list; it's empty by default, but make sure.
$ alias foo1="echo $1"
$ alias foo2="echo '$1'"
$ alias -p
The output of alias -p is as follows:
$ alias -p
alias foo1='echo '
alias foo2='echo '\'''\'''
Notably, the $1s are not present at all -- because you defined the alias in a double-quoted string, they were replaced with the current value of $1 -- the value present in the current shell's context, which for an interactive shell is going to be empty at startup.
Thus, when you run:
foo2 hello world
...what the shell invokes is:
echo '' hello world
Because echo puts spaces between its arguments, this prints out as:
hello world
Whereas, if you run:
foo1 hello world
...what the shell invokes is:
echo hello world
...because, as you can see in the foo1 alias emitted by alias -p, there is no record of the unquoted $1 left; it was replaced with its current value -- an empty string -- at definition time.
aliases do not take positional arguments. $1 will evaluate to nothing inside an alias. foo2 is literally appending what you typed after foo2 to the empty string '', which includes the space after foo2. foo1 is not appending to anything as $1 evaluates to nothing, so the space is not shown.

Ignore "$" at the beginning of the command in Bash

On many websites, "$" is written at the beginning when introducing the Linux command.
But of course, this will result in a "$: command not found" error.
To avoid this it is necessary to delete or replace "$" every time, but it is troublesome.
So, if the beginning of the input command is "$", I think that it would be good if I could ignore "$", is it possible?
If you really need this, you can create a file in a directory that is in your $PATH. The file will be named $ and will contain
#!/bin/bash
exec "$#"
Make it executable, then you can do
$ echo foo bar
foo bar
$ $ echo foo bar
foo bar
$ $ $ echo foo bar
foo bar
$ $ $ $ echo foo bar
foo bar
Note that this does not affect variable expansion in any way. It only interprets a standalone $ as the first word in the command line as a valid command.
I just noticed a problem with this: It works for calling commands, but not for shell-specific constructs:
$ foo=bar
$ echo $foo
bar
$ $ foo=qux
/home/jackman/bin/$: line 2: exec: foo=qux: not found
and
$ { echo hello; }
hello
$ $ { echo hello; }
bash: syntax error near unexpected token `}'
In summary, everyone else is right: use your mouse better.
Yes it is possible for you to ignore the command prompt, when copying commands from web sites. Use the shift and arrow keys to ignore the prompt. This will also help you to ignore the use of the # sign, which is used to indicate commands, which need administrative privileges.

How do I set variables from verbatim shell output?

I thought this should be simple, but now that I'm trying it I can't figure it out. Did I take stupid pills this morning?
I have a command with output that is some variables that I want set. I thought I could use eval, but that apparently doesn't work.
Here's what I want to do:
$ ./foo
FOO=bar
BAR=baz
$ eval ./foo
$ echo $FOO
bar
How do I set those directly?
You need to evaluate the output of the script, not the string ./foo
$ eval $( ./foo )
What you try todo is sourcing the foo file into your shell. You can do that by invoking dot command and give your file as argument (the file does not have to be executable)
$ . ./foo
$ echo $FOO
bar

printf, ignoring excess arguments?

I noticed today Bash printf has a -v option
-v var assign the output to shell variable VAR rather than
display it on the standard output
If I invoke like this it works
$ printf -v var "Hello world"
$ printf "$var"
Hello world
Coming from a pipe it does not work
$ grep "Hello world" test.txt | xargs printf -v var
-vprintf: warning: ignoring excess arguments, starting with `var'
$ grep "Hello world" test.txt | xargs printf -v var "%s"
-vprintf: warning: ignoring excess arguments, starting with `var'
xargs will invoke /usr/bin/printf (or wherever that binary is installed on your system). It will not invoke bash's builtin function. And only a builtin (or sourcing a script or similar) can modify the shell's environment.
Even if it could call bash's builtin, the xargs in your example runs in a subsell. The subshell cannot modify it's parent's environment anyway. So what you're trying cannot work.
A few options I see if I understand your sample correctly; sample data:
$ cat input
abc other stuff
def ignored
cba more stuff
Simple variable (a bit tricky depending on what exactly you want):
$ var=$(grep a input)
$ echo $var
abc other stuff cba more stuff
$ echo "$var"
abc other stuff
cba more stuff
With an array if you want individual words in the arrays:
$ var=($(grep a input))
$ echo "${var[0]}"-"${var[1]}"
abc-other
Or if you want the whole lines in each array element:
$ IFS=$'\n' var=($(grep a input)) ; unset IFS
$ echo "${var[0]}"-"${var[1]}"
abc other stuff-cba more stuff
There are two printf's - one is a shell bultin and this is invoked if you just run printf and the other is a regular binary, usually /usr/bin/printf. The latter doesn't take a -v argument, hence the error message. Since printf is an argument to xargs here, the binary is run, not the shell bulitin. Additionally, since it's at the receiving end of a pipeline, it is run as a subprocess. Variables can only be inherited from parent to child process but not the other way around, so even if the printf binary could modify the environment, the change wouldn't be visible to the parent process. So there are two reasons why your command cannot work. But you can always do var=$(something | bash -c 'some operation using builtin printf').
Mat gives an excellent explanation of what's going on and why.
If you want to iterate over the output of a command and set a variable to successive values using Bash's sprintf-style printf feature (-v), you can do it like this:
grep "Hello world" test.txt | xargs bash -c 'printf -v var "%-25s" "$#"; do_something_with_formatted "$var"' _ {} \;

Resources