What platform independent way to find directory of

2019-02-23 21:28发布

问题:

According to POSIX:

http://pubs.opengroup.org/onlinepubs/9699919799/utilities/sh.html

there are some cases where it not obvious. For example:

If the file is not in the current working directory,
the implementation may perform a search for an executable
file using the value of PATH, as described in Command Search and Execution.

My Bash 4.x doesn't follow this optional rule (due to security concern??) so I can't test how it be in real life...

What platform independent way to find directory of shell executable in shell script?

PS. Also dirname $0 case fail with:

#!/bin/sh
echo $0
dirname $0

when you:

$ sh runme.sh
runme.sh
.

So you need something like:

CMDPATH=`cd $(dirname $0); echo $PWD`

To made code dependent only on built-in shell capabilities I rewrite code to:

PREVPWD=$PWD
cd ${0%${0##*/}}.
CMDPATH=$PWD
cd $PREVPWD

This look ugly but doesn't require fork any executables...

回答1:

EDIT3:

Though not strictly POSIX yet, realpath is a GNU core app since 2012. Full disclosure: never heard of it before I noticed it in the info coreutils TOC and immediately thought of this question, but using the following function as demonstrated should reliably, (soon POSIXLY?), and, I hope, efficiently provide its caller with an absolutely sourced $0:

% _abs_0() { 
> o1="${1%%/*}"; ${o1:="${1}"}; ${o1:=`realpath -s "${1}"`}; eval "$1=\${o1}"; 
> }  
% _abs_0 ${abs0:="${0}"} ; printf %s\\n "${abs0}"
/no/more/dots/in/your/path2.sh

EDIT4: It may be worth highlighting that this solution uses POSIX parameter expansion to first check if the path actually needs expanding and resolving at all before attempting to do so. This should return an absolutely sourced $0via a messenger variable (with the notable exception that -s will preserve symlinks) as efficiently as I could imagine it could be done whether or not the path is already absolute.

EDIT2:

Now I believe I understand your problem much better which, unfortunately, actually renders most of the below irrelevant.

(minor edit: before finding realpath in the docs, I had at least pared down my version of this not to depend on the time field, but, fair warning, after testing some I'm less convinced ps is fully reliable in its command path expansion capacity)

On the other hand, you could do this:

ps ww -fp $$ | grep -Eo '/[^:]*'"${0#*/}"

eval "abs0=${`ps ww -fp $$ | grep -Eo ' /'`#?}"

I need to fix it to work better with fields instead of expecting the time field to come just before the process's path and relying on its included colon as a reference, especially because this will not work with a colon in your process's path, but that's trivial and will happen soon, I think. The functionality is otherwise POSIX compliant, I believe. Probably parameter expansion alone can do what is necessary, I think.

Not strictly relevant (or correct):

This should work in every case that conforms to POSIX guidelines:

echo ${0%/*}

EDIT:

So I'll confess that, at least at first blush, I don't fully understand the issue you describe. Obviously in your question you demonstrate some familiarity with POSIX standards for variable string manipulation via parameter expansion (even if your particular implementation seems slightly strained at a glance), so it's likely I'm missing some vital piece of information in my interpretation of your question and perhaps, at least in its current form, this is not the answer you seek.

I have posted before on parameter expansion for inline variable null/set tests which may or may not be of use to you as you can see at the "Portable Way to Check Emptiness of a Shell Variable" question. I mention this mainly because my answer there was in large part copied/pasted from the POSIX guidelines on parameter expansion, includes an anchored link to the guidelines coverage on this subject, and a few examples from both the canonical documentation and my own perhaps less expertly demonstrated constructs.

I will freely admit however, that while I do not yet fully understand what it is you ask, I don't believe that you will find a specific answer there. Instead I suspect you may have forgotten, as I do occasionally, that the # and % operators in POSIX string manipulation are used to specify the part of the string you want to remove, not that you wish to retain as some might find more intuitive. What I mean is any string slice you search for in this way is designed to disappear from your output, which will then be only what your remains of your original string after your specified search string is removed.

So here's a bit of an overview:

Whereas a single instance of either operator will remove only as little as possible to fully satisfy your search, but when doubly instanced the search is called in a greedy form and removes as much of the original string as your search could possibly allow.

Other than that you need only know some basic regex and remember that # begins its search for your removal string from the left and scans through to the right, and that % begins instead its search from the right and scans through to the left.

## short example before better learning if I'm on the right track
## demonstrating path manipulation with '#' and '%'
% _path_one='/one/two/three/four.five'
% _path_two='./four.five'
## short searching from the right with our wildcard to the right 
## side of a single character removes everything to the right of 
## of the specified character and the character itself
## this is a very simple means of stripping extensions and paths
% echo ${_path_one%.*} ${_path_one%/*}
/one/two/three/four   /one/two/three
## long searching from the left with the wildcard to the left of
## course produces opposite results 
% echo ${_path_one##*.} ${_path_one##*/}
five    four.five

## will soon come back to show more probably


回答2:

I believe you can get it using readlink:

scriptPath=$(readlink -f -- "$0")
scriptDirectory=${scriptPath%/*}