Bash shortcut to get parent of last used parameter - bash

In my workflow I do very usually:
cat this/very/long/filename.txt
cd !$
bash: cd: this/very/long/filename.txt: Not a directory
Which is to be expected :(
And now I recover the last command, remove manually the file part, and repeat the cd, which now works. That is too much typing!
It would be sooo nice if there was a bash shortcut like:
cd !§
Which could give me the parent of the last used parameter. I know !§ does not exist, I just wished it did! Is there something which can satisfy this?

Et voilà. History modifiers come to the rescue!
$ cd !!:$:h
which can be abbreviated to
$ cd !$:h
This command takes the last argument of the previous command, and removes the trailing path name.
In more details:
!! expands to the previous command
:$ expands to the last argument of the previous command
:h takes the header; that is, removes the file name (which is the trailing part of the above last argument)
As an aside,
!!:$:t
does exactly the opposite.
For an in-depth discussion, please refer to the Bash documentation.

This shorter version would also work:
cd !$:h
Details:
!$ is synonymous to !!:$ which presents the last argument of the previous command.
: separates the modifier from the event-word designator.
h is the modifier that removes the trailing file name component.

Building on the answer presented by several other people,
the ideal workflow is to type cd !$:h after the cat this/very/long/filename.txt command. 
This answer, cd !$:h, will still work after cd !$, but,  once you’ve made that mistake,
you can use the even shorter !:h, which repeats the last command (cd this/very/long/filename.txt),
but taking only the head of the last word.
I'm using these little-known shorthands:
!word_designator is equivalent to !!:word_designator
for non-numeric word_designators (e.g., !$ is equivalent to !!:$) and
!:modifier is equivalent to !!:modifier
(e.g., !:h is equivalent to !!:h).

In this case, I usually do: (I have emacs key binding)
cd Alt-dotAlt-b(n times)Ctrl-K
better:
cdAlt-dotALT-Backspace(n times)
note alt-b or ALT-Bs could be pressed multiple times, till you are satisfied with the path.

cat this/very/long/filename.txt
cd !!^:h
cd this/very/long
!! refers to the last command
^ first argument, second word(word designators)
:h remove trailing pathname component, leaving the head(modifers).

Instead of navigating through the history, you can also consider the use of the $_ variable to refer to the last argument of the previous command (see _ under the Special Parameters section of bash(1)).
To further strip the filename component ("everything after, and including, the last slash"), use parameter expansion (Remove matching suffix pattern):
$ cat this/very/long/filename.txt
$ cd ${_%/*}
The last command will be cd this/very/long since /filename.txt got stripped. A difference with history expansion is that this command (cd ${_%/*}) gets added to the history.

Related

Is there an alias to refer to the last referenced file in the terminal?

Currently, I'm running 2 commands in order like so:
nano ~/.zshrc
source ~/.zshrc
is there a shortcut like !! to reference the last referenced file? So that the terminal gets source ~/.zshrc without me retyping the path of the last file?
Just found the answer. Weird how that happens.
It's called a "bang history command". According to
Bash history reuse and bang commands
What is more important and convenient is that you can extract
arguments from previous commands (see below).
For example: !!:1
designates the first argument of the last command. This can be
shortened to !1.
!!:$ designates the last argument of the preceding
command. This may be shortened to !$.

What does !!:2 mean in Mac or Linux?

Today I saw a command in Mac:
touch !!:2/{f1.txt, f2.txt}
I know the use of touch command but what does !!:2 does in this command. I don't have Mac and tried in Linux It is giving some weird output. If anyone could explain more expression like this it would be great.
touch updates file timestamp (to current time, given no arguments)
!! is 'History expansion' operation, retrieving previous command from bash history log in this form (two exclamation dots), alias for '!-1'
:2 is word specifier, retrieving 2nd command argument. E.g. if previous history command was ls -l /tmp, !!:2 will render to '/tmp'
{f1.txt,f2.txt} is called 'Brace expansion'. Brace expansion requires single word string without unescaped white spaces (it's definitely a typo in the question). For example, foo{bar,baz} will be expanded to 'foobar foobaz'
So, let's assume we run bash command
ls -l /tmp
Now, touch !!:2/{f1.txt,f2.txt} will produce
touch /tmp/f1.txt /tmp/f2.txt
https://tiswww.case.edu/php/chet/bash/bashref.html
!! refers to the previous command. This is a synonym for ‘!-1’.
:2 refers to the second argument.
So for example :
echo "content" > foo
cp foo bar
cat !!:2
Displays the content of bar.
!!:2 is the second argument of the previous command.
Which one was it in your example?

How to rename file with bash by adding first symbol - dot?

I would like to rename my file from myscript.js to .myscript.js. How can I do it with bash? I've tried different options like mv myscript.js \.*, but it doesn't work. I've tried to experiment by adding non-dot symbol like - mv myscript.js m*, but now I don't even know where my file is (of course, I have a copy ;).
You're trying too hard.
mv myscript.js .myscript.js
Brace expansion
mv {,.}myscript.js
You can use rename utility to rename all *.js file by placing a dot before them:
rename 's/(.+\.js)/.$1/' *.js
Or in pure BASH use this for loop:
for i in *.js; do mv "$i" ".$i"; done
Don't use patterns as the target of mv (or cp for that matter). The command won't do what you want or expect, most of the time: Bash will expand the pattern at the moment when you run the command, using the file names as they were before your command was executed. So .* matches nothing (since the file doesn't exist, yet) and m* will match any file or folder which starts with m. Since you didn't get an error, chances are that the last match was a folder.
There is no way to access the previous parameter of the current command. If you want to avoid typing too much, then use Tab for both file names (so you end up with mv myscript.js myscript.js) and then use Ctrl+Left and Ctrl+Right to move quickly to the start of the second argument to insert the missing ..
You can access the parameters of the previous command, though. But that's not a feature of BASH - it's a feature of readline.

How can I recall a command using ! without executing it immediately?

In Bash, typing for example !make followed by Enter will recall and also execute the last command that starts with "make" in the history.
What I'd like is to just recall that command, but not evaluate it right away until I hit Enter again, to allow me to look at it, or edit it if needed.
Is there a way to do that in Bash?
You can use CTRL+p to get the previous command
You can also use CRL+r to reverse search for a command in your history.
The p modifier after any history expansion prints the result to the command line for further editing, rather than executing it.
$ echo foo
foo
$ !!:p
$ echo foo
^
|
cursor remains here
You can expand history on the current line using the history-expand-line key (M-^).
The M modifier key is usually mapped to Alt.
$ !make
AltShift6
$ make -C mydir
if your last make command was make -C mydir. You can then edit the command line in place.
Alternatively, you can scroll through history as outlined in other answers.

Can I have a shell alias evaluate a history substitution command?

I'm trying to write an alias for cd !!:1, which takes the 2nd word of the previous command, and changes to the directory of that name. For instance, if I type
rails new_project
cd !!:1
the second line will cd into the "new_project" directory.
Since !!:1 is awkward to type (even though it's short, it requires three SHIFTed keys, on opposite sides of of the keyboard, and then an unSHIFTed version of the key that was typed twice SHIFTed), I want to just type something like
cd-
but since the !!:1 is evaluated on the command line, I (OBVIOUSLY) can't just do
alias cd-=!!:1
or I'd be saving an alias that contained "new_project" hard-coded into it. So I tried
alias cd-='!!:1'
The problem with this is that the !!:1 is NEVER evaluated, and I get a message that no directory named !!:1 exists. How can I make an alias where the history substitution is evaluated AT THE TIME I ISSUE THE ALIAS COMMAND, not when I define the alias, and not never?
(I've tried this in both bash and zsh, and get the same results in both.)
For bash:
alias cd-='cd $(history -p !!:1)'
Another way to accomplish the same thing:
For the last argument:
cd Alt-.
or
cd Esc .
For the first argument:
cd Alt-Ctrl-y
or
cd Esc Ctrl-y
For zsh:
alias cd-='cd ${${(z)$(fc -l -1)}[3]}'
How this works:
$(fc -l -1) is evaluated. fc -l {start} [{end}] means «list history commands from {start} till {end} or last if {end} is not present».
${(z)...} must split ... into an array just like the shell does (see «Parameter Expansion Flags» in man zshexpn), but in fact it splits on blanks. Maybe it is only my bug.
${...[3]} takes third value from the array. First value is a number of a command, second is command and third and later are arguments.

Resources