3

I'd like to get the name of the immediate parent directory of a given file, e.g. foo given /home/blah/foo/bar.txt, using a parameter expansion. Right now I can do it in two lines:

f="/home/blah/foo/bar.txt"
dir_name="${f%/*}"
immediate_parent="${dir_name##*/}"

But I'm very new to parameter expansions, so I assume this could be optimized. Is there a way to do it in only one line?

ethan.roday
  • 2,485
  • 1
  • 23
  • 27
  • 1
    @j.a. It's a near-duplicate in that this question focuses on if and how the same problem can be solved using a specific feature, namely parameter expansion, even though the answer happens to be: "can't be done", and even though my answer here by happenstance ended up using essentially the same approach as your answer to the linked question. The value of this post is in showing _why_ it cannot (reasonably) be done _with parameter expansion_. – mklement0 Dec 05 '15 at 20:09

1 Answers1

5

You can't do it with a single parameter expansion, but you can use =~, Bash's regex-matching operator:

[[ $f =~ ^.*/(.+)/.+$ ]] && immediate_parent=${BASH_REMATCH[1]}

Note: Assumes an absolute path with at least 2 components.

If calling an external utility is acceptable, awk offers a potentially simpler alternative:

immediate_parent=$(awk -F/ '{ print $(NF-1) }' <<<"$f")

As for why it can't be done with a single parameter expansion:

  • Parameter expansion allows for stripping either a prefix (# / ##) or a suffix (% / %%) from a variable value, but not both.

    • Nesting prefix- and suffix-trimming expansions, while supported in principle, does not help, because you'd need iterative modification of values, whereas an expansion only ever returns the modified string; in other words: the effective overall expansion still only performs a single expansion operation, and you're again stuck with either a prefix or a suffix operation.
  • Using a single parameter expansion, extracting an inner substring can only be done by character position and length; e.g., a hard-coded solution based on the sample input would be: immediate_parent=${f:11:3}

    • You can use arithmetic expressions and even command substitutions as parameter expansion arguments, but the pointlessness of this approach - at least in this scenario - becomes obvious if we try it; note the embedded command substitutions - the first to calculate the character position, the second to calculate the length:

      immediate_parent=${f:$(d=${f%/*/*}; printf %s ${#d})+1:$(awk -F/ '{ print length($(NF-1)) }' <<<"$f")}
      
mklement0
  • 382,024
  • 64
  • 607
  • 775