views:

82

answers:

5

I have a file whose complete path is like

/a/b/c/d/filename.txt

If I do a basename on it, I get filename.txt. But this filename is not too unique.

So, it would be better if I could extract the filename as d_filename.txt i.e.

{immediate directory}_{basename result}

How can I achieve this result?

+3  A: 
file="/path/to/filename"
echo $(basename $(dirname "$file")_$(basename "$file"))

or

file="/path/to/filename"
filename="${file##*/}"
dirname="${file%/*}"
dirname="${dirname##*/}"
filename="${dirname}_${filename}"
Dennis Williamson
I have a lot of different files inside the folder hierarchy, so I don't know the name of the folders beforehand.
Lazer
@eSKay: Stumbled upon this old question, but thought to point out that this answer does not depend on knowing the names beforehand; just change the `/path/to/filename` in either solution to the the path to your file and use the rest verbatim - it'll work.
Arkku
@Arkku: thanks!
Lazer
+1  A: 
$ FILE=/a/b/c/d/f.txt
$ echo $FILE
/a/b/c/d/f.txt
$ echo $(basename ${FILE%%$(basename $FILE)})_$(basename $FILE)
d_f.txt
pulegium
I wish I knew how that worked, but it does! Many thanks.
IanVaughan
+1  A: 

don't need to call external command

s="/a/b/c/d/filename.txt"
t=${s%/*}
t=${t##*/}
filename=${t}_${s##*/}
ghostdog74
+1  A: 

This code will recursively search through your hierarchy starting with the directory that you run the script in. I've coded the loop in such a way that it will handle any filename you throw at it; file names with spaces, newlines etc.

Note*: the loop is currently written to not include any files in the directory that this script resides in, it only looks at subdirs below it. This was done as it was the easiest way to make sure the script does not include itself in its processing. If for some reason you must include the directory the script resides in, it can be changed to accommodate this.

Code

#!/bin/bash

while IFS= read -r -d $'\0' file; do
    dirpath="${file%/*}"
    filename="${file##*/}"
    temp="${dirpath}_${filename}"
    parent_file="${temp##*/}"

    printf "dir: %10s  orig: %10s  new: %10s\n" "$dirpath" "$filename" "$parent_file"
done < <(find . -mindepth 2 -type f -print0)

Test tree

$ tree -a
.
|-- a
|   |-- b
|   |   |-- bar
|   |   `-- c
|   |       |-- baz
|   |       `-- d
|   |           `-- blah
|   `-- foo
`-- parent_file.sh

Output

$ ./parent_file.sh
dir:  ./a/b/c/d  orig:       blah  new:     d_blah
dir:    ./a/b/c  orig:        baz  new:      c_baz
dir:      ./a/b  orig:        bar  new:      b_bar
dir:        ./a  orig:        foo  new:      a_foo
SiegeX
This will not always work. /a/1/b/c/d/baz and /a/2/b/c/d/baz wil yield duplicate the duplicate names 'd_baz'.
vladr
This is true, but I would say your comment would be more appropriate for the OP than myself as my code meets the requirements as stated.
SiegeX
+1  A: 

Take the example:

 /a/1/b/c/d/file.txt
 /a/2/b/c/d/file.txt

The only reliable way to qualify file.txt and avoid conflicts is to build the entire path into the new filename, e.g.

 /a/1/b/c/d/file.txt -> a_1_b_c_d_file.txt
 /a/2/b/c/d/file.txt -> a_2_b_c_d_file.txt

You may be able to skip part of the beginning if you know for sure that it will be common to all files, e.g if you know that all files reside somewhere underneath the directory /a above:

 /a/1/b/c/d/file.txt -> 1_b_c_d_file.txt
 /a/2/b/c/d/file.txt -> 2_b_c_d_file.txt

To achieve this on a per-file basis:

# file="/path/to/filename.txt"
new_file="`echo \"$file\" | sed -e 's:^/::' -e 's:/:_:g'`"
# new_file -> path_to_filename.txt

Say you want do do this recursively in a directory and its subdirectories:

# dir = /a/b
( cd "$dir" && find . | sed -e 's:^\./::' | while read file ; do
  new_file="`echo \"$file\" | sed -e 's:/:_:g'`"
  echo "rename $dir/$file to $new_file"
done )

Output:

rename /a/b/file.txt to file.txt
rename /a/b/c/file.txt to c_file.txt
rename /a/b/c/e/file.txt to c_e_file.txt
rename /a/b/d/e/file.txt to d_e_file.txt
...

The above is highly portable and will run on essentially any Unix system under any variant of sh (inclusing bash, ksh etc.)

vladr