0

I tried using a while loop but I messed that up.

#!/bin/bash
parent=$(dirname "$1")
while ["$parent" -ne "/"]
do
    parent=$(dirname "$parent")
done  
echo "$parent"

the path can be any length and is passed in via console.

./script.bash /file/path/can/be/huge.bash the desired output is file

Mort
  • 3,379
  • 1
  • 25
  • 40
M2bandit
  • 99
  • 1
  • 9

4 Answers4

3

I'm a bit confused, I think this is all you want.

#!/bin/bash
echo "$1" | sed -e 's=^/==' -e 's=/.*=='

Or

#!/bin/bash
path=${1#/}
echo "${path%%/*}"

With the first case, since it is a one-liner, you really probably don't need an external script.

If the path can have a leading . (e.g. ./file/path/can/be/huge.bash), this will need to be slightly modified.

If the path can be huge, the last thing you surely want is to loop.

Mort
  • 3,379
  • 1
  • 25
  • 40
  • You need to use double quotes in `echo "$1"` and `echo "${path%%/*}"` in order to be robust against values which contain irregular whitespace or shell metacharacters. – tripleee Aug 31 '17 at 04:26
  • I tried figuring out the second option just didn't have the syntax right. I've seen things like that before but had no clue what to search for for the information on it. Is there something I can google for more info on that? My unix admin class just started off assuming we know these things. Thank you. – M2bandit Aug 31 '17 at 04:40
  • 1
    @M2bandit, google "bash parameter substitution". The `tldp` pages are always good. – Mort Aug 31 '17 at 14:23
  • @tripleee, I agree 100% with your sentiment, and I should have added quotes in my example. However, in this case, I do not believe they are necessary. But if you can find an example that doesn't work, I'd be interested to know. – Mort Aug 31 '17 at 14:25
  • Create a file named `all * your \t base !are $belong to $(us)` (literally, except with a literal tab character where I have `\t`) and run your script on that. – tripleee Aug 31 '17 at 15:00
  • Somebody set us up the bomb, @tripleee, you're right. I'm surprised the `*` shell expands without an `eval`. I'll have to look that up. – Mort Aug 31 '17 at 15:43
  • It's well-documented, and about half of the FAQ on the Bash wikis is variants of this. See also https://stackoverflow.com/questions/10067266/when-to-wrap-quotes-around-a-shell-variable – tripleee Aug 31 '17 at 15:50
1

You almost had it:

#!/bin/bash
parent=$(dirname "$1")
# Use until instead of while (just a style thing to avoid having negated logic)
# Use == for string comparison
# Use [[ ]] because it's a shell builtin and generally is nicer to use
# Use $(dirname "$parent") instead of $parent. Without it, the loop runs until
# parent=/, and that destroys any useful information
# Now, it runs until the parent of $parent is /, which is what you wanted
until [[ $(dirname "$parent") == "/" ]]; do
    parent=$(dirname "$parent")
done
# Strip off the leading /
# ${var#pat} means "strip off the shortest prefix of $var that matches pat"
parent=${parent#/}
echo "$parent"

Do note that this will fall into an infinite loop on the input .. You should check for an absolute path in the beginning.

HTNW
  • 27,182
  • 1
  • 32
  • 60
  • How will that work with **relative pathnames**? Perhaps a `parent="$1"; tmp="$(dirname "$parent")"; while [ "$parent" != "$tmp" ]; do parent="$tmp"; tmp="$(dirname "$parent")"; done` ... ? – David C. Rankin Aug 31 '17 at 03:07
  • I did say that this loops forever on relative pathnames. Handling them should be easy: attach relative part to the current dir; run off the new path. – HTNW Aug 31 '17 at 11:43
  • Yep, that was just an example of inch worming up the path. I added a complete example below. – David C. Rankin Aug 31 '17 at 21:33
0

Just using bash I would have started where HTNW finished :)

parent=${1#/}        # strip leading slash, if there is one
echo "${parent%%/*}" # strip everything from slash closest to the start til end
grail
  • 914
  • 6
  • 14
0

Regardless whether you use dirname or use parameter expansion with substring removal (e.g. parent="${1%/*}") to incrementally lop off the final pathspec, you need to test for several conditions to handle both absolute and relative pathnames. Specifically you need to determine if:

  • the path name is absolute, e.g. "/path/to/file"
  • the path name is relative, e.g. "path/to/file"
  • the path name references the parent dir using ".."
  • the path name references the current dir using "."

To do that you can use compound statements e.g. [ ... ] && [ ... ] ... (or the older, less favored [ ... -a .... -a .... ]). You also need to consider whether to use bashisms (non-POSIX, bash only functions and test clauses, like [[ ... ]]), or to make your script portable limiting your syntax to POSIX compatible elements. (parameter expansion is POSIX compliant)

Putting all those pieces together, you can do something similar to the following:

#!/bin/bash

parent="$1"
tmp="$(dirname "$parent")"

## if only filename given, parent is "./"
if [ "$tmp" = "." ]; then
    parent="./"
else
    ## find parent for absolute and relative paths
    while [ "$parent" != "$tmp" ] && [ "$tmp" != "/" ] && 
          [ "$tmp" != "." ] && [ "$tmp" != ".." ]; do
        parent="$tmp"
        tmp="$(dirname "$parent")"
    done
fi

printf "%s\n%s\n" "$1" "$parent"

Example Use/Output

$ bash parent.sh ../a/b/c.d
../a/b/c.d
../a

$ bash parent.sh ./a/b/c.d
./a/b/c.d
./a

$ bash parent.sh /a/b/c.d
/a/b/c.d
/a

$ bash parent.sh a/b/c.d
a/b/c.d
a

$ bash parent.sh c.d
c.d
./

note: you can use tmp="${parent%/*}" in place of tmp="$(dirname "$parent")", it is up to you. (you just need to adjust the first test for filename only to [ "$parent" = "$tmp" ] and replace "$tmp" != "/" with "$tmp" != "")

You can be as creative as you like isolating the parent. There isn't one right way and all others are wrong. Just try and use good script tests and validations to cover as many corner cases as you are likely to encounter.

If you wanted to use parameter expansion and use the old conditional -a operator, you could do:

#!/bin/bash

parent="$1"
tmp="${parent%/*}"

## if only filename given, parent is "./"
if [ "$parent" = "$tmp" ]; then
    parent="./"
else
    ## find parent for absolute and relative paths
    while [ "$parent" != "$tmp" -a "$tmp" != "" -a \
            "$tmp" != "." -a "$tmp" != ".." ]; do
        parent="$tmp"
        tmp="${parent%/*}"
    done
fi

printf "%s\n%s\n" "$1" "$parent"

Look things over and let me know if you have further questions.

David C. Rankin
  • 81,885
  • 6
  • 58
  • 85