Check if shell variable contains an absolute path
Solution 1
You can just do:
case $1 in (/*) pathchk -- "$1";; (*) ! : ;; esac
That should be enough. And it will write diagnostics to stderr and return failure for inaccessible or uncreatable components. pathchk
isn't about existing pathnames - it's about usable pathnames.
The
pathchk
utility shall check that one or more pathnames are valid (that is, they could be used to access or create a file without causing syntax errors) and portable (that is, no filename truncation results). More extensive portability checks are provided by the-p
option.By default, the
pathchk
utility shall check each component of eachpathname
operand based on the underlying file system. A diagnostic shall be written for eachpathname
operand that:
Is longer than
{PATH_MAX}
bytes (see Pathname Variable Values in <limits.h>)Contains any component longer than
{NAME_MAX}
bytes in its containing directoryContains any component in a directory that is not searchable
Contains any character in any component that is not valid in its containing directory
The format of the diagnostic message is not specified, but shall indicate the error detected and the corresponding
pathname
operand.It shall not be considered an error if one or more components of a
pathname
operand do not exist as long as a file matching thepathname
specified by the missing components could be created that does not violate any of the checks specified above.
Solution 2
[ "$1" != "${1#/}" ] || return 1
There may be a better way (that's why I asked). This code strips off any leading /
in $1
and checks that the result is not the same as $1
itself.
Solution 3
An absolute path would
- begin with
/
- not contain any
/../
or/./
- not begin with
../
or./
- not end with
/..
or/.
so you could do this (portably) with a case statement:
case "x$1" in (x*/..|x*/../*|x../*|x*/.|x*/./*|x./*) rc=1 ;; (x/*) rc=0 ;; (*) rc=1 ;; esac return $rc
This intentionally excludes things such as
/../../../foo/../../../bar
which a naive "leading slash" interpretation permits.
For a concise definition of absolute path, refer to realpath in POSIX.
Solution 4
Pattern matching is done with case
statements in all Bourne-like shells.
is_absolute() { case "$1" in ///* | //) true;; //*) false;; # on some systems, //foo is special and is # not an absolute path. // alone is / /*) true;; *) false esac }
Remove the first two entries on systems that don't treat //foo
specially.
Solution 5
If by absolute path you mean that it starts with /
, and we are talking about bash
(as tag suggest):
$ var1='/tmp/foo'
$ var2='tmp/foo'
$ [[ "$var1" =~ ^/ ]] && echo yes || echo no
yes
$ [[ "$var2" =~ ^/ ]] && echo yes || echo no
no
Related videos on Youtube
Wildcard
Updated on September 18, 2022Comments
-
Wildcard almost 2 years
I want to check if a shell variable contains an absolute path.
I don't care if the path exists or not—if it doesn't I'm going to create it—but I do want to ensure that I'm dealing with an absolute pathname.
My code looks something like the following:
myfunction() { [ magic test to see if "$1" is an absolute path ] || return 1 mkdir -p "$(dirname "$1")" || return 1 commands >> "$1" }
Or, the use case where the absolute path to be verified is intended to be a directory:
anotherfunction() { [ same magic test ] || return 1 mkdir -p "$1" dostuff >> "$1/somefile" }
If this were
awk
I would do the check like so:myvar ~ /^\//
There must be a clean way to do this with the shell's string handling, but I'm having trouble coming up with it.
(Mentioning a
bash
-specific solution would be fine but I'd like to know how to do this portably, also. POSIX string handling seems like it should be sufficient for this.) -
mikeserv over 8 yearsyeah... that's one way. I wouldn't call it better - and the
return
is unnecessary. The thing is with this - and the other - there is still a possibility for inaccessible/unreadable/unwritable intermediate components that would preclude path creation for all trailing components. -
Wildcard over 8 yearsWhat's the
x
for? -
vonbrand over 8 yearsAn absolute path starts at
/
, period. You can navigate up (..
) if you want, still absolute. -
Wildcard over 8 years@mikeserv, right—but this is just a sanity check. The creation of the path components is handled by
mkdir -p
, so it just remains to check exit status of that command. Point is that I don't want to runmkdir
at all on a relative path name, where the command could succeed, but the accessibility of thevar
be dependent on my current working directory. -
Marius over 8 yearsI put the "x" first, in case the shell does not like the first character.
-
Wildcard over 8 yearsThat's simple enough.
bash
only, right? -
Wildcard over 8 years@mikeserv then I misunderstood your comment. Unreadable/unwritable intermediate components...I assumed you meant because of the filesystem, or permissions issues, etc. I'm just trying to validate the contents of the variable as being a pathname I would want to create. (i.e. a string check.)
-
jimmij over 8 years@Wildcard yes
[[
is bash syntax, may work in other shells as well like zsh. -
mikeserv over 8 yearsRight - but there's a command for that.
-
Wildcard over 8 yearsI wasn't aware of the
pathchk
command. Perfect. I'll leave open for a while longer to see if anything better shows up, but I think you nailed it—case
switch plus a command actually designed to check a pathname. :) -
mikeserv over 8 years@Wildcard - it's pretty handy. Adding
-pP
can also be used to single out paths with weird characters and other riffraff. -
cuonglm over 8 years@ThomasDickey: It's too complicated, POSIX define absolute path as a pathname beginning with a single or more than two
/
. -
mikeserv over 8 yearswell, i did kind of mean because of permissions, yes. if the path string branches out to an inaccessible component - its still no good to you. and if parts of are too long to be valid the same is true. but you really don't need
return
there:false || return 1
is just redundant, you know? the[ test ]
already returns true or false... -
Marius over 8 yearsPOSIX says more than one thing about pathnames, you can use whatever interpretation you prefer.
-
Wildcard over 8 years@mikeserv—but this is in a function body.
return
in this context skips the remaining commands of the function. -
Ivan Kovtun over 8 yearsShouldn't
/./
be rejected (based on the same premises of/./././
) ? -
mikeserv over 8 yearsoh. much more necessary in that case, then.
-
mikeserv over 8 years@cuonglm - excellent point - I kind of already rulled out the
-
dash, huh...? -
mikeserv over 8 yearsyou can use
[ ! "${1%%/*}" ]
as well. -
cuonglm over 8 years@mikeserv:
-P
seems to be useless here, since when any path like/path/to/-_start_with_dash
is fine. -
mikeserv over 8 years@cuonglm - here, yes. but not useless. for example - usernames can be validated with
pathchk -Pp
. And you can dofn(){ pathchk -P "$@"; }
to get a list of the arguments that start w/ - printed to stderr. -
cuonglm over 8 years@mikeserv: Fair point, I also realized that my answer need
-P
for empty path. -
mikeserv over 8 years@cuonglm - true. or
[ ${1:+"!"} "${1%%/*}" ]
-
cuonglm over 8 years@mikeserv: tricky, as always!
-
Angel Todorov over 8 yearsIf you prefer to use glob matching over regular expressions:
[[ $var == /* ]]
-- quotes are not strictly required within double brackets. -
cuonglm over 8 years@Wildcard: No, even without double bracket, then double quote
${var:0:1}
, it's not POSIX.${var:0:1}
is'n in POSIX. -
mikeserv over 8 years@Wildcard:
[ "${1%"${1#/}"}" ]
is the POSIX way. -
gardenhead over 8 yearsYeah this wasn't meant to be POSIX compliant, there are already good answers for that. This is just the clearest way in Bash IMO.
-
Wildcard over 8 yearsWhoops! Removed inaccurate comment; thanks. Not POSIX but very clear, yes.
-
mikeserv over 8 yearsi dont consider that it is more clear than the
${1%"${1#/}"}
substitution. It simply substitutes away the results of substituting away the first character if it is a/
. i never know what the numbers do with${var:num:num}
. personally. you could do[ / = "${1%"${1#?}"}" ]
if you liked, but it doesn't add anything useful. With${1%"${1#/}"}
if the first char is not a slash the expansion is null, but if it is a slash it expands only to the slash. It's pretty straightforward. For that mattercase $1 in /*) ;; esac
also works inbash
and is a damn sight clearer than${1:0:1}
. -
Marius over 8 yearssure - added that. I agree that
pathchk
tests for the overall pathname length, but embedded relative-pathname syntax has far more importance to my work. -
Stéphane Chazelas over 8 yearsWhat you're checking for is a canonical (though not checking for symlinks) absolute path. Anything that starts with
/
(with the exception of//foo
on some systems) is an absolute path. An absolute path is a path that is not relative. -
Stéphane Chazelas over 8 yearsI'm not aware of any shell that would require that
x
. Note that the(*)
syntax (as opposed to*)
) though POSIX (and I prefer it as well) is not understood by the Bourne shell. -
Marius over 8 yearsIt would be nice if someone actually pointed to a page in POSIX which gave a precise definition. I've been using the sense in
realpath
for quite a while. (I have encountered problems with pathnames containing spaces - perhaps you overlooked that). I'll stick with the(*)
syntax - no reason to argue about that. -
mikeserv over 8 yearsi always thought that was for windows machines.
-
cuonglm over 8 years@StéphaneChazelas: Yes, that's why I use
-P
forpathchk
. -
Marius over 8 yearsThat link goes only to the top-level page... Perhaps you meant this.
-
mikeserv over 8 yearsoh. im sorry. i sometimes do that with the frames on that page. here. oh. yes. that is what i meant.
-
mikeserv over 8 yearsyeah. i like it for the way it can be used on shell globs to return a code and write the names to stderr if any files matched would turn up unexpected characters. and it doesn't have to be used on pathnames - arbitrary strings are fine if you expect them to be standard
word
types. -
Stéphane Chazelas over 8 years@mikeserv, I've just asked that question
-
mikeserv over 8 yearswhat a coincidence - ive just upvoted it! i always thought it was used for the win32 POSIX layer and the
\\.?Volume
stuff - or however that's supposed to go - though with forward slashes of course. -
mikeserv over 8 yearsa note about that other question, though? its your question of course, but it seems a damned shame to mention Cygwin and skip UWIN. Were it me, for preference, I would do the reverse...
-
Stéphane Chazelas over 8 years[OT] @mikeserv, I can't tell, I've never used UWIN. Cygwin has been good enough for me for the rare times I've had to use a Windows system. Can you easily get a X server or sshd with UWIN?
-
mikeserv over 8 yearsWell, it comes with them and installs them as services. You then just enable them. What little I use MS for usually involves family and is usually in a VM. I nested an install of UWIN - and, unlike Cygwin, its familiar. Its also got all of the rest of the ksh93 advanced POSIX style things going on. Ummm ... I think sshd comes with it - maybe I'm misremembering - doesn't jive with ksh's cosh deal though, huh? Definitely X does though.
-
Stéphane Chazelas over 8 years@mikeserv, d'oh sorry.
-
mikeserv over 8 years@StéphaneChazelas - that's what I said, too. ^ its up there somewhere.
-
mikeserv over 8 yearsWOW! you can put links in CODE BLOCKS!?!? that's awesome. i had no idea...
-
Stéphane Chazelas over 8 years@Wildcard, no,
[[...]]
comes from ksh.=~
was first added by bash IIRC but later copied by ksh93 and zsh (different syntaxes though).[[ $var1 = /* ]]
would work in all ksh variants and versions, bash and zsh.case $var in /*)
is the Bourne/POSIX standard one. -
Wildcard over 8 years@StéphaneChazelas, does word splitting get skipped for a
case
switch or should that becase "$1" in
? -
Stéphane Chazelas over 8 years@Wildcard, there can't be word splitting as it's not a list context. Quotes won't harm though.