What is the unix history command "!$" in bash? - linux

I'm trying to understand what is the unix command !$.
For example, I know that the command !1 is used to run the history command number 1.
It seems like !$ runs the last command typed in the bash.
For example if I wrote mv folder12 folder123 and then I would write cd !$ I would actually preform cd folder123.
Is it correct that !$ runs the last command typed in the bash?

!$ matches the last argument of the previous command.
From man bash
yank-last-arg (M-., M-_)
Insert the last argument to the previous command (the last word of
the previous history entry). With an argument, behave exactly
like yank-nth-arg. Successive calls to yank-last-arg move back
through the history list, inserting the last argument of each line in
turn. The history expansion facilities are used to extract the last
argument, as if the "!$" history expansion had been specified.
Example
$ vi a
$ ls -l !$ # expands to "ls -l a"
-rw-rw-r-- 1 me me 30 18 abr. 22:00 a
See also What is your single most favorite command-line trick using Bash?:
I'm a fan of the !$, !^ and !* expandos, returning, from the most
recent submitted command line: the last item, first non-command item,
and all non-command items. To wit (Note that the shell prints out the
command first).
And also a good reading: The Definitive Guide to Bash Command Line History.

Related

Bash: Using filenames as commands with the wildcard

I was playing around in my terminal earlier and discovered that I can execute single word commands ('ls','cat','python2.7','exit') by making a file or directory named the same thing as the command. However, I can't execute 'multi-word' commands ('rm\ -rf *','ls -a', 'python2.7\ test.py') (which also led me to discover that you can't remove directories named '-rf' with 'rm -rf *') UNLESS the arguments are in alphabetic order. Seemingly, when you just pass in * to bash it reads the names of the files/directories in alphabetical order, passing each successive name in as an argument to the previous command. Example:
$ mkdir cat
$ touch dog
$ vim dog # at this point I put 'Hello!' into dog
$ *
Hello!
Why does the wildcard character not allow me to execute commands of files with spaces in them but does allow me to execute commands of multiple file names? Thanks!
EDIT: Also aliases don't work for some reason. Example:
$ alias lsa='ls -a'
$ mkdir lsa
$ *
No command 'lsa' found, did you mean:
...
lsa: command not found
Anyone know why this is?
Every command bash processes is subject to several varieties of expansions before bash interprets and acts on the result. The last of those is pathname expansion, wherein bash examines the words of the command (as produced by previous expansions and word splitting) for any that contain any of the unquoted special characters *, ?, and [, and interprets those words as patterns representing file names. After all expansions are performed, any redirections and variable assignments in the expanded command are performed and removed from the command. If any words remain, the first is taken as the name of a command to run. The provenance of that word does not matter.
In pathname expansion, the * matches any string, including the empty string. When bash performs pathname expansion on the word consisting of only *, it expands to the names of all the files in the working directory, except those beginning with a dot (.).
Thus, if your working directory contains just a single file named ls, and you execute the command *, bash expands that command to ls and executes it, producing the output ls. Similarly, if the contents of the directory are echo, Hello,, and World!, then when you execute the command * bash expands it to the equivalent of echo Hello, 'World!', and the command outputs Hello, World!.
That doesn't work as you thought it would for filenames with spaces in them because each file name matching a pattern is expanded to a single word. These are not subsequently split. (Bash does perform "word splitting" as part of the expansion process, but that happens before pathname expansion.) This is normally what you want, for otherwise commands such as rm * would not reliably do what you expect.
That doesn't work with aliases because bash expands aliases when it reads commands, not when it executes them. That has several implications, but one of them is that aliases are interpreted before any expansions are performed. Another is that the replacement text of an alias is subject to all the normal expansions.

what does this linux command do: "cd !!:1"

In linux command cd, after make a dir, I can use "cd !!:1" to enter it, but what exactly does !!:1 mean, I can't search it by google, because it include special chars.
That will change directory to the first argument of the previous command
For example
% ls foo bar
% cd !!:1
is equivalent to
% ls foo bar
% cd foo
Also !!:0 gives you the actual command (less arguments), !!:2 the second argument, !!:$ the last argument, and !! the whole command line.
!! is short hand for the previous command. The :1 goes to the second parameter in the command, which in your previous command was the directory name. One of my favorite command-line shortcuts is sudo !!

Alternative to Up Arrow + Enter to run previous command?

Sometimes I have to run a command many times in succession, for example to see if a service has started, and it becomes tedious to move my hands away from my normal typing position to press the Up Arrow and Enter keys repeatedly. Is there a way to run the previous command without the Up Arrow and Enter keys, perhaps with an elaborate shell script?
I've tried the following, but it is unsatisfactory because it cannot execute aliases, and it is a little slow.
history | tail -2 | head -1 | cut -d' ' -f4- | cat > prev_command.txt
sleep .01
chmod 777 prev_command.txt
eval prev_command.txt
rm prev_command.txt
Ideally I'd have an alias to this script so I can type in something like "prev" in the command line and hit Enter to run the previous command again.
In bash, you can press ctrlp to go to the previous command -- that's a lot better than having to move to the arrow keys.
See also: https://github.com/fliptheweb/bash-shortcuts-cheat-sheet/
Use
!!
to run your previous command.
sudo !!
also works , for the record.
Instead of running the same command many times in succession, why not watch it instead? watch will run a specified command repeatedly and display the output in stdout so you can see it change over time.
watchcommand
I often use the "history expansion" feature in bash (usually activated with cntlR) -- it interactively searches through your history for the previous closest match.
See the bash manual section Searching for Commands in the History, and also Using History Interactively.
Are you an emacs or vi user? You can use
set -o vi
set -o emacs
to set emacs or vi keybindings. You can then use the emacs or vi key bindings in bash. I don't know if this should work for other shells. I believe the vi mode starts in insert mode, so you need to hit esc to enter command mode. In emacs mode (the default), you can use ctrl+p and then ctrl+j to move to the previous line and do a carriage return.
Otherwise, you can use !! as someone else suggested.
In bash:
$ help fc
fc: fc [-e ename] [-lnr] [first] [last] or fc -s [pat=rep] [command]
Display or execute commands from the history list.
fc is used to list or edit and re-execute commands from the history list.
FIRST and LAST can be numbers specifying the range, or FIRST can be a
string, which means the most recent command beginning with that
string.
Options:
-e ENAME select which editor to use. Default is FCEDIT, then EDITOR,
then vi
-l list lines instead of editing
-n omit line numbers when listing
-r reverse the order of the lines (newest listed first)
With the `fc -s [pat=rep ...] [command]' format, COMMAND is
re-executed after the substitution OLD=NEW is performed.
A useful alias to use with this is r='fc -s', so that typing `r cc'
runs the last command beginning with `cc' and typing `r' re-executes
the last command.
Exit Status:
Returns success or status of executed command; non-zero if an error occurs.
Note the suggestion for alias r; I use this frequently.
Depending on what terminal you're using, I know a lot used to have F3 as an option for repeating, but that's still outside the normal range for typing as well unless you have a special keyboard with more accessible function keys.
My keyboard makes the function keys easily accessible, but I don't do much command line work in unix any more, so I wouldn't be able to tell you for sure whether or not this is still possible.

Can I execute nested or chained commands in UNIX shell?

Can I execute command within another command in UNIX shells?
If impossible, can I use the output of the previous command as the input of next command, as in:
command x then command y,
where in command y I want use the output of command x?
You can use the backquotes for this.
For example this will cat the file.txt
cat `echo file.txt`
And this will print the date
echo the date is `date`
The code between back-quotes will be executed and be replaced by its result.
You can do something like;
x=$(grep $(dirname "$path") file)
here dirname "$path" will run first and its result will be substituted and then grep will run, searching for the result of dirname in the file
What exactly are you trying to do? It's not clear from the commands you are executing. Perhaps if you describe what you're looking for we can point you in the right direction. If you want to execute a command over a range of file (or directory) names returned by the "find" command, Colin is correct, you need to look at the "-exec" option of "find". If you're looking to execute a command over a bunch of arguments listed in a file or coming from stdin, you need to check out the "xargs" commands. If you want to put the output of a single command on to the command line of another command, then using "$(command)" (or 'command' [replace the ' with a backquote]) will do the job. There's a lot of ways to do this, but without knowing what it is you're trying it's hard to be more helpful.
Here is an example where I have used nested system commands.
I had run "ls -ltr" on top of find command. And it executes
it serially on the find output.
ls -ltr $(find . -name "srvm.jar")

Shell Script - Linux

I want to write a very simple script , which takes a process name , and return the tail of the last file name which contains the process name.
I wrote something like that :
#!/bin/sh
tail $(ls -t *"$1"*| head -1) -f
My question:
Do I need the first line?
Why isn't ls -t *"$1"*| head -1 | tail -f working?
Is there a better way to do it?
1: The first line is a so called she-bang, read the description here:
In computing, a shebang (also called a
hashbang, hashpling, pound bang, or
crunchbang) refers to the characters
"#!" when they are the first two
characters in an interpreter directive
as the first line of a text file. In a
Unix-like operating system, the
program loader takes the presence of
these two characters as an indication
that the file is a script, and tries
to execute that script using the
interpreter specified by the rest of
the first line in the file
2: tail can't take the filename from the stdin: It can either take the text on the stdin or a file as parameter. See the man page for this.
3: No better solution comes to my mind: Pay attention to filenames containing spaces: This does not work with your current solution, you need to add quotes around the $() block.
$1 contains the first argument, the process name is actually in $0. This however can contain the path, so you should use:
#!/bin/sh
tail $(ls -rt *"`basename $0`"*| head -1) -f
You also have to use ls -rt to get the oldest file first.
You can omit the shebang if you run the script from a shell, in that case the contents will be executed by your current shell instance. In many cases this will cause no problems, but it is still a bad practice.
Following on from #theomega's answer and #Idan's question in the comments, the she-bang is needed, among other things, because some UNIX / Linux systems have more than one command shell.
Each command shell has a different syntax, so the she-bang provides a way to specify which shell should be used to execute the script, even if you don't specify it in your run command by typing (for example)
./myscript.sh
instead of
/bin/sh ./myscript.sh
Note that the she-bang can also be used in scripts written in non-shell languages such as Perl; in the case you'd put
#!/usr/bin/perl
at the top of your script.

Resources