Paste the last output and edit it in bash - bash

I like to use bash (on linux) without touching mouse.
I often encounter the following situation.
$ locate libfreetype.a
/usr/lib/x86_64-linux-gnu/libfreetype.a
$ cd /usr/lib/x86_64-linux-gnu
In this case, I copy /usr/lib/x86_64-linux-gnu/ and paste it using mouse or type it. I do not want to do that.
Ideally, the output of locate libfreetype.a is stored in somewhere (maybe in killring??) and paste it with C-y command and edit it on terminal.
Are there good way to do this?
(Just for this example case, there are smart one-line commands. But those are not the desired answers. I want a general solution.)
Another example
Suppose that I remember that there is a memo... in the same directory as libfreetype.a but I forgot the directory name.
$ locate libfreetype.a
/usr/lib/x86_64-linux-gnu/libfreetype.a
$ nano /usr/lib/x86_64-linux-gnu/memo # Tab completion here
$ nano /usr/lib/x86_64-linux-gnu/memo_xxx.txt
if I could cache the output /usr/lib/x86_64-linux-gnu/libfreetype.a and paste it, things are very easy.
(nano $(dirname $(locate libfreetype.a))/memo_xxx.txt works for this case, but if I want to change the path itself, I need to think another technique.)

As noted in the comments, there probably no common way to do this in terminal. But it's possible to redirect the output of the command to program that copy stdin to clipboard, e. g. xclip. If you want to insert and edit copied text in terminal, you need to remove newline characters before copying. Consider following script:
copy.bash
#!/bin/bash
tr '\n' ' ' | xclip
Usage:
$ locate libfreetype.a | copy
$ cd # now press <shift> + <insert>
$ cd /usr/lib/x86_64-linux-gnu/libfreetype.a # continue editing
The xclip command copies its input for pasting into X applications.
The tr '\n' ' ' command translates all newlines into spaces. You need this if you want to paste the text into command line. It strips the trailing newline and joins lines if output contains more than one. If use plain xclip all newline characters are pasted literally, which causes bash to run command immediately after pasting and doesn't allow to edit it.
If output of the command (e. g. locate) is multi-line and you want to choose only one of them to copy (instead of copying all), you can use iselect. iselect reads input and shows the interactive menu for selecting a line/lines and prints it to the standart output.
Use it like this:
$ locate pattern | iselect -a | tr '\n' ' ' | xlip
# locate prints several lines
# iselect allows user to select one line interactively
# the result is copied to clipboard
$ # <shift> + <insert>
This also can be a script:
icopy.bash
#!/bin/bash
iselect -am | tr '\n' ' ' | xclip
(the -m option allows to choose several lines instead of one)
Usage:
$ locate pattern | icopy
Disadvantages of these approaches:
it works only with X sessions since xlcip need the X session to be running
you need to install new software (xclip and, optionally, iselect)
you need to redirect output explicitly, before running the command; so, technically, it cannot be considered as answer. But it is the best solution I have found for myself.
BTW, here is the script on my local machine that I really use quite often:
$ cat ~/bin/copy
#!/bin/bash
paste -sd\ | tr -d '\n' | xsel --clipboard
echo "Copied: $(xsel --clipboard --output)" >&2
$ echo hello | copy
Copied: hello
Links: man iselect, man xclip, man tr, yank.

You can run script (man script) from your .bashrc which generates a live log of your session's output. And bind a shortcut for opening the log file in an editor, so you can insert yanked text back into $READLINE_LINE.
But script captures raw output from interactive programs (such as editors), so if script could be modified to skip interactive output, it would work. Next step would be parsing output, to make navigation faster.
Here is a .bashrc snippet that does this for non-interactive tools only: https://asciinema.org/a/395092

I noticed that a solution to this problem is given by a terminal emulator kitty. We can use a feature called "hints" and keyboard shortcuts configured by default.
As in the original question, let's think of the situation.
$ locate libfreetype.a
/usr/lib/x86_64-linux-gnu/libfreetype.a
$ # you want to input /usr/lib/x86_64-linux-gnu here
If you are using kitty, you can type ctrl+shift+p and then l.
You will enter a mode to select a line from the screen. When you can select the previous line, it is pasted into the current terminal input.
The detail if found in the official documentation.
The configuration associated with the action is written like this.
map ctrl+shift+p>l kitten hints --type line --program -
This means that kitten hints --type line --program - is the command mapped from ctrl+shift+p followed by l.

you could use
!$
like
shell$ echo myDir/
myDir/
shell$ cd !$
cd myDir/
shell$ pwd
/home/myDir

Related

How can I pipe output into another command?

I have a script located at /usr/local/bin/gq which is returned by the command whereis gq, well almost. What is actually returned is gq: /usr/local/bin/gq. But the following gives me just the filepath (with some white space)
whereis gq | cut -d ":" -f 2
What I’d like to do is be able to pipe that into cat, so I can see the contents. However the old pipe isn’t working. Any suggestions?
If you want to cat the contents of gq, then how about:
cat $(which gq)
The command which gq will result in /usr/local/bin/gq, and the cat command will act on that.

How do I open a file in VS Code terminal by partially matching the file name?

If I have a file named w5_align_example.cpp, how do I open that file in VS Code integrated terminal by only supplying the word align?
code w5_align_sample.cpp would open it but I sometimes only remember the keyword align unless I search in a separate command to see what the file begins with. I want to open in a single command instead.
I've tried:
$ ls | grep "align" | code which gives me Run with 'code -' to read output from another program (e.g. 'echo Hello World | code -'). error.
$ ls | grep "align" | code - opens up a new file called code-stdin-sfd.txt with the text w5_align_example.cpp inside.
What would be the simplest (i.e. shortest) command to do this?
ls | grep "align" | xargs -I{} code {}
or
code $(ls | grep "align")
You can just use *. It matches any string and can be used multiple times.
code *align*
In some shells, you can combine this with tab completion. Just type:
code *align*
And then press Tab. This will fill in the rest of the file name, but it will beep if there is more than one option.

Changing file extensions for all files in a directory on OS X

I have a directory full of files with one extension (.txt in this case) that I want to automatically convert to another extension (.md).
Is there an easy terminal one-liner I can use to convert all of the files in this directory to a different file extension?
Or do I need to write a script with a regular expression?
You could use something like this:
for old in *.txt; do mv $old `basename $old .txt`.md; done
Make a copy first!
Alternatively, you could install the ren (rename) utility
brew install ren
ren '*.txt' '#1.md'
If you want to rename files with prefix or suffix in file names
ren 'prefix_*.txt' 'prefix_#1.md'
Terminal is not necessary for this... Just highlight all of the files you want to rename. Right click and select "Rename ## items" and just type ".txt" into to the "Find:" box and ".md" into the "Replace with:" box.
The preferred Unix way to do this (yes, OS X is based on Unix) is:
ls | sed 's/^\(.*\)\.txt$/mv "\1.txt" "\1.md"/' | sh
Why looping with for if ls by design loops through the whole list of filenames? You've got pipes, use them. You can create/modify not only output using commands, but also commands (right, that is commands created by a command, which is what Brian Kernighan, one of the inventors of Unix, liked most on Unix), so let's take a look what the ls and the sed produces by removing the pipe to sh:
$ ls | sed 's/^\(.*\)\.txt$/mv "\1.txt" "\1.md"/'
mv "firstfile.txt" "firstfile.md"
mv "second file.txt" "second file.md"
$
As you can see, it is not only an one-liner, but a complete script, which furthermore works by creating another script as output. So let's just feed the script produced by the one-liner script to sh, which is the script interpreter of OS X. Of course it works even for filenames with spaces in it.
BTW: Every time you type something in Terminal you create a script, even if it is only a single command with one word like ls or date etc. Everything running in a Unix shell is always a script/program, which is just some ASCII-based stream (in this case an instruction stream opposed to a data stream).
To see the actual commands being executed by sh, just add an -x option after sh, which turns on debugging output in the shell, so you will see every mv command being executed with the actual arguments passed by the sed editor script (yeah, another script inside the script :-) ).
However, if you like complexity, you can even use awk and if you like to install other programs to just do basic work, there is ren. I know even people who would prefer to write a 50-lines or so perl script for this simple every-day task.
Maybe it's easier in finder to rename files, but if connected remotely to a Mac (e.g. via ssh), using finder is not possible at all. That's why cmd line still is very useful.
Based on the selected and most accurate answer above, here's a bash function for reusability:
function change_all_extensions() {
for old in *."$1"; do mv $old `basename $old ."$1"`."$2"; done
}
Usage:
$ change_all_extensions txt md
(I couldn't figure out how to get clean code formatting in a comment on that answer.)
No need to write a script for it just hit this command
find ./ -name "*.txt" | xargs -I '{}' basename '{}' | sed 's/\.txt//' | xargs -I '{}' mv '{}.txt' '{}.md'
You do not need a terminal for this one; here is a sample demonstration in MacOS Big Sur.
Select all the files, right-click and select "rename..."
Add the existing file extension in "Find" and the extension you want to replace with "Replace with".
And done!
I had a similar problem where files were named .gifx.gif at the end and this worked in OS X to remove the last .gif:
for old in *.gifx.gif; do
mv $(echo "$old") $(echo "$old" | sed 's/x.gif//');
done
cd $YOUR_DIR
ls *.txt > abc
mkdir target // say i want to move it to another directory target in this case
while read line
do
file=$(echo $line |awk -F. '{ print $1 }')
cp $line target/$file.md // depends if u want to move(mv) or copy(cp)
done < abc
list=ls
for file in $list
do
newf=echo $file|cut -f1 -d'.'
echo "The newf is $newf"
mv $file $newf.jpg
done

Fast way to edit nth line of previous command output

I often find find myself doing a workflow like this:
$ find . |grep somefile
./tmp/somefile.xml
./test/another-somefile.txt
(review output)
$ vim ./tmp/somefile.xml
Now, it would be neat if there was some convenient way of using the output of the find command and feed it to vim.
The best I've come up with is:
$ nth () { sed -n $1p; }
$ find . |grep somefile
./tmp/somefile.xml
./test/another-somefile.txt
(review output)
$ vim `!!|nth 2`
I was wondering if there are other, maybe prettier, ways of accomplishing the same thing?
To clarify, I want a convenient way of grabbing the nth line from a previously run command to quickly open that file for editing in vim, without having to cut & paste the filename with the mouse or tab-complete my way through the file path.
way 1: don't pass exact file to vim, but the whole output. choose the file in vim
currently you are working in two steps:
1 - launch the find/grep... cmd
2 - vim !!....
if you are sure that you want to use vim to open one (or more) file(s) from the find result. you may try:
find. (with grep if you like) |vim -
then you have the whole output in vim, now you can use vim magic to move cursor to the file you want to edit, then press gf. (I do this sometimes)
way 2: refine your regex in your find (or grep), to get the single file, that you want to edit.
this is not a hard thing at all. then you can just vim !!.
your nth() is nice. however imagine there are 30 lines in output, and your file sits in the line# 16. how do you count it? sure you can add |nl at the end, then you cannot directly use !! any longer..
just my 2 cents
Modified after your comment. Not sure if it's "convenient" though..
command | tail -n3 | head -n1 | xargs vim
Maybe this is what you're looking for?
find . -name "*somefile*" -exec vim -p {} \;
If you want an interactive review maybe you can use something like this:
TMP_LIST=""; for i in `find . | grep somefile`; do echo $i; read -p "(y/n)?"; [ $REPLY == "y" ] && TMP_LIST="$TMP_LIST $i"; done; vim $TMP_LIST
You almost did it!!
pearl.251> cat file1
a b c d e f pearl.252> find . -name "file*"
./file1
./file2
./file3
./file4
./file5
./file6
./file7
pearl.253> vi `!!|awk 'NR==1'`
the last line overe here will open the file1 in vi.

Bash history re-runs: possible command to avoid using !bang!?

Scenario:
You are doing your daily Bash shell stuff. You want to run a previous command so you type:
history | grep foocommand
Then you get a list of all the foocommand stuff you did for however long your history has kept track, in a list like so:
585 foocommand --baz --bleet
750 foocommand | grep quux
987 history grep | foocommand
You decide you want to run command 585, so you type
!585
Question: Just for curiosity, is there a way to take that final step out of the equation and still get the expected outcome? It would be nice if there were a way to say:
"grep through history and automatically run the first item on the list"
or
"grep through history and let me choose which item to run using the arrow keys"
Press ^r (that's CTLR-r), type foocommand press Enter ;)
That will search history and show the latest matching entry
Did you try '!f'? 'f' as first letter of the foocommand.
Personally, I use an alias derived from ksh:
alias r="fc -e -"
('r' for 'rerun', I suppose...)
I can rerun the last command starting with 'foo':
r foo
I can rerun commands 46 to 50:
r 46 50
I can rerun just command 585:
r 585
The only gotcha is that if you typed:
$ cd somewhere
then running 'r cd' won't work because there was an extra space in front of the 'cd' command. The 'fc' command underlies history, and you can edit commands 46 to 50 before rerunning them by typing:
fc 46 50
and so on for the other variants.
(The '-e -' in the alias means 'edit with the null editor'; you could write 'fc -e vim' to edit with vim, but most people set VISUAL or EDITOR or FCEDIT or all three to make that unnecessary.)
For the rest, being a 'vim' person (fanatic?), I use 'set -o vim' and then the search facility: ESC and
/grep\ -e\ 'whatever
to search history for a command containing "grep -e 'whatever". I can repeat the search, moving ever further backwards in history, or reverse direction after overshooting, or ... I assume that the emacs mode has an equivalent search mechanism.
The syntax
!foo
will run the last command that began with foo.
Failing that, bash uses the readline library for input, which supports the history-search-forward and history-search-backward commands that can be bound to keys of your choice. I've edited my ./inputrc file to bind them to F8 and Shift-F8, so that they work like Windows' equivalent feature in the console when I connect with PuTTY:
"\e[19~":history-search-backward
"\e[32~":history-search-forward
"grep through history and automatically run the first item on the list"
Okay.
`history | grep foocommand | head -1 | tr -s ' ' | cut -d ' ' -f 3-`
Give it a try, it works like a champ!

Resources