How to open files relative to home directory - ruby

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.

Related

Why *nix command line enable execution via path relative to current directory?

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.

What is the bash equivalent of `modpath` in csh?

This is the error that I got.
modpath: Command not found.
I'm suspecting that it's because modpath is a csh command but not the right syntax for bash. So, I want to know what's its equivalent in bash. Thanks!
modpath - change global search path for dynamically loadable kernel modules
modpath allows users with appropriate privilege to modify the global search path used to locate object files for dynamically loadable kernel modules. The search path modifications take effect immediately and affect all subsequent loads for all users on the system.
pathname may be either a colon-separated list of absolute pathnames or NULL. If the former, these path names represent directories which should be searched for all autoloads of loadable kernel modules and for demand loads (see modload(2)) where the module is given by a simple file name. This list of directories will be prepended to the existing list of directories and so will be searched before any directories given in previous calls to modpath and before the default location which is always searched last. The directories do not have to exist on the system at the time modpath is called, or when a load actually takes place. If pathname is equal to NULL, the global search path is set back to its initial default value, /stand/dlkm/mod.d.
Notes: modpath is currently implemented as a macro.
[source: http://modman.unixdev.net/?sektion=2&page=modpath&manpath=HP-UX-11.11]
Based on the documentation now included in the question, modpath has nothing to do with $PATH, and it's not specific to csh, bash, or any other shell.
On my system (Ubuntu 16.10), there is no command, system call, or library function by that name, and there appears to be no installable package that provides it. The documentation you quoted is for HP-UX, which is a different flavor of UNIX. I suspect it's a system call that exists only on HP-UX.
Since you've been able to invoke modpath from csh, there's probably a command that's some kind of wrapper around the system call, though the documentation is for the system call itself, not for the command.
If such a command exists, you should be able to find it by typing
which modpath
from csh. If that gives you the full path to the command, then from bash you can either use that full path to invoke the command, or you can add the appropriate directory to your $PATH. (You very likely have a different setting for $PATH in csh vs. bash.)

Make filename as command

I have a file that have shell commands
For eg: test
I need to run this as
#test
instead of #./test
Anyone please help!
Commands that don't have / in them are tried as
shell builtins
functions
aliases
executables in each component of $PATH
PATH is an environment variable that contains a colon-separated list of paths where the shell or or some of the exec system functions search for executable files.
PATH=$PWD:$PATH
Prepends the current directory, making your test executable runnable.
(As hek2mgl mentions, appending is normally much better for security reasons as you normally don't want user-writable paths to override write-protected paths that are already part of PATH--though you do want it in this case because test is already a system-level executable)
It will still be preceded by the test shell builtin, however.
For that reason, you should name it something other than test, or circumvent the builtin with $(which test) after you modify your PATH.
In a shell, you could also make it a function (or an alias ) that would invoke the full path. That wouldn't have many bad security implications.
Note:
$PWD is the absolute path to your current directory. You could also use . (or another relative path), making the PATH "change" as you cd along, however that is considered a bad practice for security reasons.

Bash:script cannot be called after adding the path

I am writing my own unix scripts so I want to add a new directory for Bash. I add sth in .bash_profile like this.
PATH="~/Documents:${PATH}"
export PATH
and in my ~/Documents, there is a file named test of which the content is
#!/usr/bin/env python3.5
print("hahahhah")
I also used
chmod 755 test
to make it executable.
But I cannot call it in terminal directly. ./test works as usual.
What went wrong?
After I change to
PATH="$HOME/Documents:${PATH}"
export PATH
nothing happens.
FDSM_lhn#9-53:~/Documents$ test
FDSM_lhn#9-53:~/Documents$ ./test
hahahhah
Solution:
The fundamental reason is that I have a command of the same name as default one, So it won't work any way! Changing name will be sufficient!
Tilde doesn't get expanded inside strings. So by quoting the right-hand side of the assignment you prevent it from being expanded and get a literal ~ in your PATH variable which doesn't help you any.
You have two ways to fix this:
Drop the quotes on the assignment (yes this is safe, even for $PATH values with spaces, etc.).
Use $HOME instead of ~.
I prefer the second solution but the first is entirely valid for this case.
Beware though that in places where you aren't doing a straight assignment you often cannot just drop the quotes and trying to use ~ will cause problems.
In which case you will end up finding a question like this with an answer like this and something ugly like this.

Getting short path for ssh

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

Resources