Unix & Linux Stack Exchange is a question and answer site for users of Linux, FreeBSD and other Un*x-like operating systems. Join them; it only takes a minute:

Sign up
Here's how it works:
  1. Anybody can ask a question
  2. Anybody can answer
  3. The best answers are voted up and rise to the top

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.)

share|improve this question
up vote 6 down vote accepted

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 each pathname operand based on the underlying file system. A diagnostic shall be written for each pathname 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 directory

  • Contains 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 the pathname specified by the missing components could be created that does not violate any of the checks specified above.

share|improve this answer
1  
I 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. :) – Wildcard Jan 20 at 1:34
    
@Wildcard - it's pretty handy. Adding -pP can also be used to single out paths with weird characters and other riffraff. – mikeserv Jan 20 at 1:41
    
@cuonglm - excellent point - I kind of already rulled out the - dash, huh...? – mikeserv Jan 20 at 2:54
2  
@cuonglm - true. or [ ${1:+"!"} "${1%%/*}" ] – mikeserv Jan 20 at 3:18
1  
@mikeserv: tricky, as always! – cuonglm Jan 20 at 3:32
[ "$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.

share|improve this answer
    
yeah... 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. – mikeserv Jan 20 at 1:26
    
@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 run mkdir at all on a relative path name, where the command could succeed, but the accessibility of the var be dependent on my current working directory. – Wildcard Jan 20 at 1:28
    
@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.) – Wildcard Jan 20 at 1:31
1  
Right - but there's a command for that. – mikeserv Jan 20 at 1:33
1  
@mikeserv—but this is in a function body. return in this context skips the remaining commands of the function. – Wildcard Jan 20 at 2:16

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.

share|improve this answer
    
i always thought that was for windows machines. – mikeserv Jan 20 at 9:39
    
@mikeserv, I've just asked that question – Stéphane Chazelas Jan 20 at 10:23
    
what 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 Jan 20 at 10:25
1  
WOW! you can put links in CODE BLOCKS!?!? that's awesome. i had no idea... – mikeserv Jan 20 at 12:15
1  
@Wildcard, there can't be word splitting as it's not a list context. Quotes won't harm though. – Stéphane Chazelas Jan 20 at 17:59

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
share|improve this answer
    
That's simple enough. bash only, right? – Wildcard Jan 20 at 1:29
    
@Wildcard yes [[ is bash syntax, may work in other shells as well like zsh. – jimmij Jan 20 at 1:32
    
If you prefer to use glob matching over regular expressions: [[ $var == /* ]] -- quotes are not strictly required within double brackets. – glenn jackman Jan 20 at 3:32
1  
@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. – Stéphane Chazelas Jan 20 at 16:08

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.

share|improve this answer
    
What's the x for? – Wildcard Jan 20 at 1:26
8  
An absolute path starts at /, period. You can navigate up (..) if you want, still absolute. – vonbrand Jan 20 at 1:27
    
I put the "x" first, in case the shell does not like the first character. – Thomas Dickey Jan 20 at 1:29
1  
@ThomasDickey: It's too complicated, POSIX define absolute path as a pathname beginning with a single or more than two /. – cuonglm Jan 20 at 1:50
1  
What 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 Jan 20 at 9:26

POSIX define absolute path as a pathname beginning with a single or more than two /.

There's a utility called pathchk to check pathname, so you can do:

[ -z "${1%%/*}" ] && pathchk -pP "$1"

-p tells pathchk to perform check for path that:

-P guard you from any path component start with - and an empty path.

share|improve this answer
1  
you can use [ ! "${1%%/*}" ] as well. – mikeserv Jan 20 at 3:12
    
Beware [ -z "${1%%/*}" ] returns true if $1 is the empty string. – Stéphane Chazelas Jan 20 at 9:17
    
@StéphaneChazelas: Yes, that's why I use -P for pathchk. – cuonglm Jan 20 at 9:39

Just check the first character of the string using substring syntax:

[[ ${var:0:1} = / ]] || return 1
share|improve this answer
    
@Wildcard: No, even without double bracket, then double quote ${var:0:1}, it's not POSIX. ${var:0:1} is'n in POSIX. – cuonglm Jan 20 at 6:29
1  
@Wildcard: [ "${1%"${1#/}"}" ] is the POSIX way. – mikeserv Jan 20 at 6:40
1  
Yeah this wasn't meant to be POSIX compliant, there are already good answers for that. This is just the clearest way in Bash IMO. – gardenhead Jan 20 at 6:45
    
Whoops! Removed inaccurate comment; thanks. Not POSIX but very clear, yes. – Wildcard Jan 20 at 6:50
    
i 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 matter case $1 in /*) ;; esac also works in bash and is a damn sight clearer than ${1:0:1}. – mikeserv Jan 20 at 7:03

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.