zsh: create file and open in editor - terminal

Does anyone know of any neat shortcuts to create a file and open it in an editor (in my case Sublime) at the same time?
Something like touch some/nice/file.txt | st ... or touch some/nice/file.txt && ...something
(st being my sublime shortcut, which can take an argument)
touch some/nice/file.txt && st some/nice/file.txt works of course, but involves repeating the file name, and it would be nice to do it without!
Thanks in advance

You can put an alias in your .zshrc to shorten it to whatever you want. Just add a line like
alias to='to(){ touch $1; st $1 }; to '
to your zshrc.
Now if you type 'to some/nice/file.txt' in your shell it should make the file and open it. You will need to reload the terminal once or use 'source .zshrc' before it can work.

Related

Opening directory in file explorer from WSL2

Im in windows terminal and I would like to open directories in file explorer, while in WSL2 Ubuntu.
I tried typing "explorer.exe Desktop/", but it opens Documents, in fact every time I try to run it, it just opens Documents, except when I type "explorer.exe .", then it opens the current directory correctly, but I want it to work with any directory I give it. Any ideas?
Edit 1: I found this function, just add it to your ~/.bashrc and it works
start(){
path=$(wslpath -w "$1")
/mnt/c/Windows/explorer.exe "$path"
}
Type start "Some Path" without quotation marks and it opens the path in file explorer, also I saw that I needed to add quotation marks around argument of explorer.exe
Answer is in post, but I will type it here again.
Put this function in your ~/.bashrc
start(){
path=$(wslpath -w "$1")
/mnt/c/Windows/explorer.exe "$path"
}
Now when you type start "Some Path" you will open it in file explorer. You can also remove /mnt/c/Windows/ from /mnt/c/Windows/explorer.exe if you want.
PTH (Path to here): Basically what my problem was that I was trying to read user input for the path to recreate the start command from cmd and powershell, but in wsl2 that was a lot harder because it doesn't have GUI so it doesn't know how to open it using xdg-open or other tools. Using read command from bash was not good enough because of the newline it always gives the user to type, but this uses arguments and takes the next thing you type in bash instantly which is awesome. Functions in bash work with arguments like lets say programs in c where you type ./program arg1 arg2 arg3..., where in bash it is the same, the number indicating the argument, so $0 is the zero-th argument which is always the name, so we don't use it. Starting from $1 $2 $3 and so on are the arguments which are usable in bash functions. In our case typing "start Desktop/", $1 is assigned "Desktop/", which is then converted to C:\Users<Your Username>\Desktop and assigned to $path. Then $path is passed to /mnt/c/Windows/explorer.exe to finally open in file explorer. Pretty nifty right? That's what I said first time 1 minute ago when I first saw and understood bash functions.
"Desktop/" doesn't actually resolve to a folder. Try the following:
explorer.exe "c:\users\<YOUR USERNAME>\Desktop"
This provides the full (absolute) path to explorer.exe.

How do I edit current shell command without executing it?

There seems to be quite a lot of information on how to edit and execute a command using your editor using "edit-and-execute-command (C-x C-e)", but what I would like to achieve is take the current shell command, apply certain filtering (using a script) and then return it to prompt for further approval/manual changes before execution. Is this possible with bash?
Latest update based on my experience
The part 0"+y$dd in the following mapping is really something that you should carefully think about and tailor it to your taste/workflow/experience.
For instance, very frequently I've found myself ending up with multiple lines in the buffer, where I only want to execute the one the cursor is on; in this case I can use 0"+y$dd:%d<CR> instead of 0"+y$dd.
And this is just one of the possible scenarios.
Final answer for those who like vim
Set vim as your EDITOR/VISUAL, so that when editing a command line, you will use vim to edit it.
Put au BufEnter /tmp/bash-fc.* nn <Leader>d 0"+y$dd:wq<CR> in your ~/.vimrc file to map Leaderd (which you will rarely use when editing a command) to the action "delete the current line into the + register without the trailing EOL".
you can use either the + or the * register in the mapping above; the ways to paste into the terminal will likely differ; you need the +clipboard option for these registers to be available.
When finished editing a command in the vim editor, hit EscapeLeaderd.
Paste the clipboard into the terminal (this is terminal-dependent).
Original answer
I often need to do the same, and I do it as follows. (I normally use the set -o vi in bash, so points 1 and 2 in the following are different if you use set -o emacs, the default; based on your question it looks like points 1 and 2 are unified in Ctrl+x followed by Ctrl+e, which is harder to type, imho.)
hit Escape to be in normal mode,
hit v to enter the editor to edit the command,
edit the command as I like,
(This is where you ask the question.)
hit Escape0"+y$dd:wq,
Note: 0"+y$, not simply "+yy, as the latter would copy the newline too, and this would result in executing the command upon pasting it in the command line,
paste the clipboard on the command line
how to do this depends on the terminal you are using, I guess; I hit Ctrl+Alt+v in URxvt.
proceed to approval/manual edit.
Clearly this is just a workaround, consisting in copying the edited command into the clipboard before deleting the whole command, so that nothing gets executed upon exiting the editor; however it's the best I can get for myself.
Update
As my EDITOR (and VISUAL) is equal to vim, when I edit the command, I edit it in vim.
In this respect, I have noticed that the buffer is named /tmp/bash-fc.random, where random is a 6-characters alphanumeric random string.
This gives space to a lot of possiblities, if you use vim as your editor, as you can define some mapping in your .vimrc to execute the whole sequence Escape0"+y$dd:wq. For instance, one command that you'd rarely use when editing a command line is Leaderd; therefore you can put the following mapping in your .vimrc file
au BufEnter /tmp/bash-fc.* nn <Leader>d 0"+y$dd:wq<CR>
so that step 4 in the above recipe becomes
hit EscapeLeaderd
It's not possible to do that in Bash/readline but it's possible in zsh
using edit-command-line command:
darkstar% autoload edit-command-line; zle -N edit-command-line
darkstar% bindkey "^X^E" edit-command-line
Now press Control-x Control-e to open your editor, edit line, leave the editor - you will see the updated command line but it will not be executed automatically.
Now that I think about it, maybe a variation of what #kenorb suggested in a comment is the best workaround (as it seems no solution exists), if we want to stick to bash.
What you can do is prepend a # (the comment character in bash) to the command, rather than echo. Then when you exit the editor, the command will be ineffective, and you will only have to press arrow up (or k, if you use set -o vi), remove the # and confirming.
Note that this strategy adds just a few keystrokes, so it can be fairly efficient, depending on your typing level.
These pieces might get you closer:
a) replace the the normal binding for newline newline (ctrl-M)
bind -x '"\C-M":date"
b) grab the current line from the history using !#
replace date with whatever script you want.
c) edit manually
d) if necessary, somehow tie in !!:p which prints the new command to the command line but does not execute it, thus letting you manually edit it.
e) using ctrl-J submit edited command rather than a newline
or they might not ....
There is an option in bash to modify command from history without executing it. I'm not sure it it's possible to use script for this, doesn't seem to be likely. Although, you can make modifications using history modifiers.
Enable option histverify to prevent execution of modified command
Use chain of modifiers to change last command
Use "!!" to put your result to command line for final edit
Here is how it looks:
$ shopt -s histverify
$ ls *.sh
script1.sh script2.sh script3.sh script-return.sh
$ !!:s/*/script1/:p
ls script1.sh
$ !!:s/1/2/:p
ls script2.sh
$ !!
$ ls script2.sh
script2.sh
I'd like to point you to the Composure framework for Bash (I'm not affiliated with it): https://github.com/erichs/composure
It provides draft and revise functions that sound like they could help with what you're trying to do. Here's a (long) quote from the project's readme file:
Composure helps by letting you quickly draft simple shell functions,
breaking down your long pipe filters and complex commands into
readable and reusable chunks.
Draft first, ask questions later
Once you've crafted your gem of a command, don't throw it away! Use
draft () and give it a good name. This stores your last command as a
function you can reuse later. Think of it like a rough draft.
$ cat servers.txt
bashful: up
doc: down
up-arrow
$ cat servers.txt | grep down
doc: down
$ draft finddown
$ finddown | mail -s "down server(s)" admin#here.com
Revise, revise, revise!
Now that you've got a minimal shell function, you may want to make it
better through refactoring and revision. Use the revise () command
to revise your shell function in your favorite editor.
generalize functions with input parameters
add or remove functionality
add supporting metadata for documentation
$ revise finddown
finddown ()
{
about finds servers marked 'down' in text file
group admin
cat $1 | grep down
}
$ finddown servers.txt
doc: down
It does not seem possible with a keyboard shortcut, at least:
$ bind -P | grep -e command -e edit
complete-command can be found on "\e!".
edit-and-execute-command can be found on "\C-x\C-e".
emacs-editing-mode is not bound to any keys
possible-command-completions can be found on "\C-x!".
vi-editing-mode is not bound to any keys
This can be done in native bash using readline specifically READLINE_LINE and READLINE_POINT variables. I use this functionality all the time though not through vim, you would need to get the value of $selected from your vim command and if not empty it takes your original line + your input and replaces your original line with the combination without executing. output as a variable
_main() {
selected="$(__coms_select__ "$#")"
origonal_text=$READLINE_LINE READLINE_LINE="${READLINE_LINE:0:$READLINE_POINT}$selected${READLINE_LINE:$READLINE_POINT}"
READLINE_POINT=$(( READLINE_POINT + ${#selected} ))
}
bind -m emacs-standard -x '"\C-e": _main '
bind -m vi-command -x '"\C-e": _main '
bind -m vi-insert -x '"\C-e": _main '
Edit
Just remembered these two utilities that will let you do this as well.
Vipe allows you to run your editor in the middle of a unix pipeline and edit the data that is being piped between programs.
vp, up, vipe, Neomux (upgrade of nvim terminal) you can do some pretty neat throwing buffers between the terminal and split window.
and Athame (full vim on the command line)
https://github.com/ardagnir/athame
careful with that one though plugins work on the cli and it can get funky if you got tons of plugins

Variable for a right-clicked item (say, a jpg) in a bash script?

I have a very simple bash script that I run often from the cli, but I've found it's frustrating to have to open a terminal, identify the right file, and run it and think the easiest way would be to run it as an option from a right-click. I am running Ubuntu 18.04 LTS.
The script is just erasing exif data, leaving the orientation tags, essentially this:
exiftool -all= -tagsfromfile # -Orientation file-*.jpg
Is there a way to have the script identify which image I'm right clicking on? I'm at a loss what to put in the file-*.jpg part which will be a variable for "whatever image I'm right-clicking on right now."
Tried searching for a good while on how to do this but am clearly either not using the right search terms or else this isn't done very often. Thank you in advance for any help!
if you want your script to run in file manager right-click menu you have to change your script and define file(s) as arguments. this happens simply by changing your file section with $1 to $n as the parameter(s).
as far as I know ubuntu uses nautilus as an file manager.
you can run nautilus-actions-config-tool either from your terminal or from dash and define your script a name and a command to run. you can follow this link for illustration learning :
ubuntu nautilus defile script in menu
for example :
#!/bin/bash
if [ "$1" != "" ]; then
echo "Positional parameter 1 contains value $1"
else
echo "Positional parameter 1 is empty"
fi
for all arguments :
#!/bin/bash
if [[ "$#" -gt 0 ]]; then
for arg in "$#"; do
echo $arg
done
fi
here is the image that shows the script worked
I know the question is a little older, but I can provide you with the solution.
You have to set up FileManager-actions, an extension for GNOME/Nautilus (but it also works for other file managers).
Setup filemanager-actions on Ubuntu 20.04
sudo apt update
sudo apt install filemanager-actions
Then run fma-config-tool and create a new action.
When creating an action, please ensure that:
[v] Display item in selection context menu
is flagged; otherwise, you will not see the context menu during the file selection.
Prepare the script you want to execute
Prepare a script that does what you need. Touch in /tmp, mv it in /usr/bin and give it execute permissions:
touch /tmp/your-script
# edit it with your editor
sudo mv /tmp/your-script /usr/bin/
sudo chmod +x /usr/bin/your-script
In your script, you can reference the filename using $1.
FILENAME=$1
echo $FILENAME
In the variable FILENAME you will find the selected file name.
Configure Nautilus-action command
To let nautilus pass the filename, insert the script path and the argument string in the' command' tab.
To fully answer your question, to let Nautilus pass the filename as a script argument, you have to specify %f.
At this point, quit the Nautilus instance and open it again:
nautilus -q
nautilus
Now, let's have a try! Right-click on a file and check out the result!
Appendix 1 - Filemanager-actions formats
%f A single file name, even if multiple files are selected.
%F A list of files. Each file is passed as a separate argument to the executable program.
%u A single URL. Local files may either be passed as file: URLs or as file path.
%U A list of URLs. Each URL is passed as a separate argument to the executable program.
%d Base directory
Here you can find a comprehensive list.
Appendix 2 - Sources
Check out my post blog in which I actually realize something similar: https://gabrieleserra.ml/blog/2021-08-14-filemanager-actions-ubuntu-20-04.html
Reference to all possible formats for FileManager-actions: https://askubuntu.com/a/783313/940068
Realize it in Ubuntu 18.04: https://askubuntu.com/a/77285/940068

Syntax error in .bash_profile unexpected end of file

Running MacOS High Sierra 10.13.5
Good day! Today I attempted to write a script in my .bash_profile to allow me to call Sublime Text with a single command, sublime.
Here is the script I produced with my limited knowledge of bash as well as some research:
sublime_open() # Function which opens the first argument as a text file in Sublime.
{
open -a /Applications/Sublime\ Text.app < $1.txt;
}
export -f sublime_open;
sublime_test() # Function which will open first argument as a text file in Sublime,
{ # creating one if it does not exist in the current directory.
if [ -e "$1" ];
then sublime_open "$1"
else
touch "$1".txt
sublime_open("$1")
fi
}
export -f sublime_test
alias sublime="sublime_test"
export sublime
I separated the function into two pieces for the sake of readability.
Expected Behavior:
sublime foo opens an instance of Sublime to the file foo.txt, creating that file if it does not already exist.
Observed Behavior:
Bash returns:
line 29: syntax error: unexpected end of file
Note that line 29 is the line after the file's final line, which does not exist.
Attempts at solution:
So far I have searched for solutions here and here.
I have tried:
Copying over to a new bash profile and deleting the old one to remove "ghost" characters
Running dos2unix on my .bash_profile
Parsing the script myself looking for individual errors, the problem seems to occur in the sublime_open() portion.
Sorry if my script is elementary or uninformed, this was my first attempt at writing a script that behaves this way. I welcome help or tips outside of the scope of my issue.
Thanks in advance!
Edit: Fixed misused heredoc as per #jwodder's solution as well as improper function calls as per #codeforester's solution.
Edit 2: If you are looking for a more elegant solution to this same problem, have a look at this very helpful gist.
The problem is this line:
open -a /Applications/Sublime\ Text.app << $1.txt;
I assume that what you mean for this to do is to feed the contents of $1.txt to Sublime Text on standard input. However, that would be done with one <, not two. With two <, you get a heredoc, which means that bash will look for the next line containing just $1.txt and will use all lines in between as the input to Sublime Text. As the file ends before bash finds a line with just $1.txt, you get an "unexpected end of file" error.
Keep in mind that on macOS, an *.app is just a folder. Generally speaking, you need to specify the location to a binary (or script). In the case of Sublime Text you have to use the Sublime Text CLI tool:
/Applications/Sublime Text.app/Contents/SharedSupport/bin/subl
See the OS X Command Line documentation for details.

Vim open a file found through grep

So I'm browsing a file in vim. I realize the function I am looking for is another file. So I do :! ack-grep "function tracking" ../ to look for it, and I see I need to examine file "../../tracking/api/ etc etc" some really long file name. what I'm doing now is trying to remember this file, type fg to get back into vim, and then :e "that really long file name". I'm wondering if there's an easier way to do this?
For instance, can I do something like vim -addtab <some file> from the command line once I've used ack-grep to find what I'm looking for, and then when I do fg to get back to vim, the file will be open in one a tab?
Awesome, lots of suggestions. I'll try them all out and in comments as I do. Then I'll pick what worked best for me once I've tried them all.
So this is the function that I settled on:
function! s:NewTabGrep(...)
let args=split(a:1)
if len(args) == 2
let dir=args[1]
else
let dir='.'
endif
echom args[0] . " " . shellescape(dir)
tabnew | execute "r ! ack-grep ". shellescape(args[0]) ." ". shellescape(dir)
endfunction
com! -nargs=? GrepTab call s:NewTabGrep('<args>')
This performs the search, and opens the results in a new vim tab. Then I can use CtrlP to open whichever file seems most interesting. Thanks to Merlin2011 for inspiration.
You can do :r ! ack-grep "function tracking" ../ to pull the output directly into the vim buffer, and then use gf to navigate to the file under the cursor.
If you want to open the file in a new tab instead, you can do Control-W, gf in normal mode.
Update: If you want the output of the command to appear in a new tab, you can do the following, which opens a new tab, and then immediately pulls the output of the command into it.
:tabnew | r!ack-grep "function tracking" ../
:vimgrep does this by default! it's the best! in fact, it's so cool, that you can even use :vim to invoke it hahaha
e.g. search all python files for "function tracking" (works best if you keep your vim working directory in your source code's root folder)
:vim /function tracking/ **/*.py
^ this will go to the first search result, and then open the quickfix window with a list of search results. I use a binding like nmap <silent> \` :QFix<CR> to quickly toggle it open and off. See... http://vim.wikia.com/wiki/Toggle_to_open_or_close_the_quickfix_window
--
however, there's a better way for navigating to the function: using CTRL-] which does an instant jump to the definition! Combined with CTRL-O and CTRL-I (jump backwards/forwards), you have an unstoppable move-around files-efficiently combo which will make you forget why you ever used IDE's =D
I googled this wiki page: http://vim.wikia.com/wiki/Browsing_programs_with_tags. One other thing you have to do is download Exuberant Ctags and build a tags file in your working directory: ctags -R ...then you're all set! (note that on Macs/BSD, it has its own ctags which wont work, you gotta use the exuberant one)
Vim has the :grep command that runs grep and stores the results into the quickfix list(you can also use :lgrep to load them to the location list). :grep also jumps to the first result, but if you don't want that you can use :grep! to just load the results to the quickfix list.
Once the results are loaded, you can use :copen to open the quickfix list(or :lopen to open the location list) and navigate between the results, or use :cfirst, :cnext, :cprevious and :clast(or :lnext, :lfirst, :lprevious and :llast) for navigation.
So, if you want to open the result in a new file, you have three options:
:grep! "function tracking" ../**/*, :copen to open the quickfix list, put cursor on the first result, ctrl-W Enter to open the result in a new window, ctrl-W T to move the window to a new tab.
:grep! "function tracking" ../**/*, :tabnew to open a new tab, :cfirst to jump to first result.
:tabnew to open a new tab, :grep "function tracking" ../**/* to perform the search and open the first result automatically.
You might find it easier to just use the quickfix list for navigation instead of opening new tabs.
:grep uses grep by default, but you can set the grepprg option to make it use ack-grep instead(you might also need to set grepformat to recognize the output). You can also use :vimgrep to do a search with Vim's regular expression without using an external program, but it will be slower.
:exec 'tab split '.fnameescape(system('ack-grep "function tracking" ../'))
should do the trick. (Make it into a function!)
I recommend you to use the ack plugin., Note that if you have vim-dispatch installed, you can search with ack.vim asynchronously(You need to write: let g:ack_use_dispatch=1 in your vimrc file). Also I recommend ag to replace ack since it's really fast.

Resources