I'm writing a shell script to be used with ssh/git.
I want to get the present directory relative to home, if possible.
The goal is to get the shortest path to be used with ssh, that is the path in context of the user.
For example, if my working directory was /home/jon/folder/other, and I was user jon with $HOME being /home/jon, I would get (~/)folder/other from the command. Tilde-slash between the parentheses optional.
So then I could give my user the instruction to use ssh at jon#server:folder/other instead of longer jon#server:/home/jon/folder/other.
Is there are trivial way to do this (which I'm feeling there is) or do I need a script to do that?
You can use the following expression:
${PWD#$HOME/}
The bash operation ${string#substring} strips substring from the front of $string (if it matches).
This might help you with future questions about bash: Recommended online resources for learning bash scripting
Related
For a long time I was under impression that *NIX shells do not support running executables via path relative to current directory. Also there're a number of posts explaining why having working directory in path is a bad practice. Still it's easy to see that following example works:
% cat<<EOF > temp/a
? #!/bin/sh
? echo "Hello looser!"
? EOF
% chmod 750 temp/a
% temp/a
Hello looser!
(I tested at CentOS and OSX).
Why it works?
Upd: it's not purely academic question, I had cases when binaries run via full path and the way described above worked different way.
Upd 2: libc execv accepts both absolute and relative paths with no problem. Hence, support for subpath/exe while failing to run exe looks as shell feature common to bash and tcsh. There must be some logic behind it??
You can always execute a program by specifying its relative path. There is no requirement that you would have to use an absolute path. Actually, the way you are executing your program, is quite common.
If you write command with no / then the shell searches $PATH for the command.
If you write dir/command with a relative directory, there's a / and the shell does not search $PATH. It interprets it relative to the current directory. dir/command always resolves to ./dir/command.
Putting . in the $PATH is dangerous because you may type command intend ing to run /usr/bin/command but actually get ./command if it exists in the current directory. Indeed, it's not just . that's dangerous: any relative path is dangerous. $PATH should only ever contain absolute paths.
So, you're saying that relative paths shall be implicitly prefixed with current directory, with exceptional case when relative path is empty that shall be treated as direction to search in $PATH?
Yes, you could say that. But I prefer to reframe it. A better way to think of it is to understand what the shell is doing versus what the kernel is doing.
Interpreting relative paths is handled implicitly by the kernel. When the shell performs a system call like execve("dir/command", ...) to execute a program the kernel knows the parent process's current directory and resolves dir/command relative to it. The shell does not have to first convert the path into an absolute one. The kernel can handle both absolute and relative paths.
The kernel knows nothing about $PATH, though. That's a shell construct. Keep in mind that the shell is a higher level piece of software. The shell decides that if you type a simple command name without a / in it, it will not simply pass that command to the kernel. If it did, typing cat would simply execute ./cat. Nobody wants that.
Instead, the shell decides that the lack of a / means it will search for the command in the $PATH. It'll search /bin, /usr/bin, $HOME/bin, etc. If you listed . in the $PATH then it would also search the current directory—don't do that! You don't want it to run ./cat. No sir.
If the shell finds an executable in the $PATH then it converts it to an absolute path name and passes that to the kernel. The kernel sees execve("/usr/bin/cat", ...).
If so and you know it from some formal specification, I'd appreciate the reference. I do need to know precisely.
See the bash man page:
If the command name contains no slashes, the shell attempts to locate it. …
If the name … contains no slashes, bash searches each element of the PATH for a directory containing an executable file by that name. …
If the search is successful, or if the command name contains one or more slashes, the shell executes the named program ….
This as nothing to do with execution. It is due to the fact that you can name a file with an absolute or a relative name, and that executables are just ordinary files.
Why is it dangerous? (.in PATH)
Think about a script that calls a command like cat foo, if you have . in the path then it could be possible to create a command cat in the same directory that would be executed in place of the original one.
To run a program in bash, I normally use relative paths because it's faster to type; for example, something like
me#host:~/dir/appX$ ./manage.py runserver
The command will then be stored in the history. To recall the command from history (CTRL+R normally), I need to be on the same path as when I ran it, making the recall function less useful.
One solution is to insert the full path the first time, but it takes a lot of writing.
me#host:~/dir/appX$ /home/me/dir/appX/manage.py runserver
Is there a way (preferably built in) to insert the current path in the command line?
Or maybe a better solution (should work on bash)?
You can do this in bash using Tilde Expansion. You need two tilde expansion related features, just showing the relevant parts from man bash below:
Tilde Expansion
If the tilde-prefix is a `~+', the value of the shell variable PWD
replaces the tilde-prefix.
tilde-expand (M-&)
Perform tilde expansion on the current word.
As it says, you can type ~+ to get the current path. And then to expand it you need to type M-&. So the key sequence ~+M-& is all you need.
I found it a little difficult pressing all these keys, so I created a key binding for this. Add a line like below in your ~/.inputrc file:
"\C-a":"~+\e&"
With this I can now type ctrl+a on my keyboard to get the current path on the command line.
PS: It's possible that ctrl+a is already bound to something else (perhaps beginning of line) in which case it might be better to use another key combination. Use bind -p to check current bindings.
Is there a way to preprocess a line entered into bash in interactive mode before it is processed by bash?
I'd like to introduce some custom shorthand syntax to deal with long paths. For example, instead of writing 'cd /one/two/three/four/five', I'd like to be able to write something like 'cd /.../five', and then my preprocessing script would replace this by the former command (if a unique directory 'five' exists somewhere below /).
I found http://glyf.livejournal.com/63106.html which describes how to execute a hook function before a command is executed. However, the approach does not allow to alter the command to be executed.
There's no good way of doing this generally for all commands.
However, you can do it for specific commands by overriding them with a function. For your cd case, you can stick something like this in your .bashrc:
cd() {
path="$1"
[[ $path == "/.../five" ]] && path="/one/two/three/four/five"
builtin cd "$path"
}
In bash 4 or later, you can use the globstar option.
shopt -s globstar
cd /**/five
assuming that five is a unique directory.
The short answer is not directly. As you have found, the PROMPT_COMMAND environment variable allows you to issue a command before the prompt is displayed, which can allow for some very creative uses, e.g. Unlimited BASH History, but nothing that would allow you to parse and replace input directly.
What you are wanting to do can be accomplished using functions and alias within your .bashrc. One approach would be to use either findutils-locate or simply a find command to search directories below the present working directory for the last component in the ellipsed path, and then provide the full path in return. However, even with the indexing, locate would take a bit of time, and depending on the depth, find itself may be to slow for generically doing this for all possible directories. If however, you had a list of specific directories you would like to implement something like this for, then the solution would be workable and relatively easy.
To provide any type of prototype or further detail, we would need to know more about how you intent to use the path information, and whether multiple paths could be provided in a single command.
Another issue arises if the directory five is non-unique...
I am using Automator on my Mac to set up a service that passes a selected folder to a bash shell script as arguments.
In the script I do:
for f in "$#"; do
printf "%q\n" "$f" | pbcopy
done
if I then do:
echo `pbpaste`
I get the path to my selected folder with spaces escaped (\). I then wanted to use this path to cd into that directory and do a bunch of other stuff (creating a blank directory structure). I hoped I could just do:
cd `pbpaste`
but this doesn't work.
If I type the path manually the cd works so I assume the is some issue with data types or returns or something??
I'll admit I don't really know what this script actually doing and may be going about this all wrong but but if anyone can explain what's going on here and how to get it working it that would be great but even better would be a pointer to a really good resource for a complete beginner to start learning about shell scripting.
I really like the idea of getting into this a bit more but all the resources I have found are either total basics (cd, ls, pwd etc) or really high level and assume a bunch of previous knowledge.
What I'd really like is a full language reference with some actual examples like you find for the languages I am more used to (HTML/CSS/JS/AS3), if such a thing exists.
Cheers for any help :)
I'm agree with #chepner's answer, but for google's results sake, to cd using pbpaste you simply do:
cd $(pbpaste)
When you use the %q format, you are adding literal backslashes to the string, which the shell does not process as escape characters when you use it with cd.
The clipboard is useful for interprocess communication; inside a single script, it's easier to just use variables to hold information temporarily. f already has the path name in it, so just use it:
cd "$f"
Notice I've quoted the expansion of f, so that any spaces in the path name are passed as part of the single argument to cd.
The following fails with Errno::ENOENT: No such file or directory, even if the file exists:
open('~/some_file')
However, I can do this:
open(File.expand_path('~/some_file'))
I have two questions:
Why doesn't open process the tilde as pointing to the home directory?
Is there a slicker way than File.expand_path?
Not sure if this was available before Ruby 1.9.3 but I find that the most elegant solution is to use Dir.home which is part of core.
open("#{Dir.home}/some_file")
The shell (bash, zsh, etc) is responsible for wildcard expansion, so in your first example there's no shell, hence no expansion. Using the tilde to point to $HOME is a mere convention; indeed, if you look at the documentation for File.expand_path, it correctly interprets the tilde, but it's a feature of the function itself, not something inherent to the underlying system; also, File.expand_path requires the $HOME environment variable to be correctly set. Which bring us to the possible alternative...
Try this:
open(ENV['HOME']+'/some_file')
I hope it's slick enough. I personally think using an environment variable is semantically clearer than using expand_path.
Instead of relying on the $HOME environment variable being set correctly, which could be a hassle when you use shared network computers for development, you could get this from Ruby using:
require 'etc'
open ("#{Etc.getpwuid.dir}/some_file")
I believe this identifies the current logged-in user and gets their home directory rather than relying on the global $HOME environment variable being set. This is an alternative solution to the above I reckon.
I discovered the tilde problem, and a patch was created to add absolute_path
which treats tilde as an ordinary character.
From the File documentation:
absolute_path(file_name [, dir_string] ) → abs_file_name
Converts a pathname to an absolute pathname. Relative paths are referenced from the current working directory of the process unless dir_string is given, in which case it will be used as the starting point. If the given pathname starts with a “~” it is NOT expanded, it is treated as a normal directory name.