Reputation: 67
I have the following script, which I normally use when I get a bunch of files that need to be renamed to the directory name which contains them.
The problem now is I need to rename the file to the directory two levels up. How can I get the grandparent directory to make this work?
With the following I get errors like this example:
"mv: cannot move ./48711/zoom/zoom.jpg
to ./48711/zoom/./48711/zoom.jpg
: No such file or directory". This is running on CentOS 5.6.
I want the final file to be named: 48711.jpg
#!/bin/bash
function dirnametofilename() {
for f in $*; do
bn=$(basename "$f")
ext="${bn##*.}"
filepath=$(dirname "$f")
dirname=$(basename "$filepath")
mv "$f" "$filepath/$dirname.$ext"
done
}
export -f dirnametofilename
find . -name "*.jpg" -exec bash -c 'dirnametofilename "{}"' \;
find .
Upvotes: 5
Views: 13044
Reputation: 437176
Note:
* This answer solves the OP's specific problem, in whose context "grandparent directory" means: the parent directory of the directory containing a file (it is the grandparent path from the file's perspective).
* By contrast, given the question's generic title, other answers here focus (only) on getting a directory's grandparent directory; the succinct answer to the generic question is: grandParentDir=$(cd ../..; printf %s "$PWD")
to get the full path, and grandParentDirName=$(cd ../..; basename -- "$PWD")
to get the dir. name only.
Try the following:
find . -name '*.jpg' \
-execdir bash -c \
'old="$1"; new="$(cd ..; basename -- "$PWD").${old##*.}"; echo mv "$old" "$new"' - {} \;
Note: echo
was prepended to mv
to be safe - remove it to perform the actual renaming.
-execdir ..\;
executes the specified command in the specific directory that contains a given matching file and expands {}
to the filename of each.
bash -c
is used to execute a small ad-hoc script:
$(cd ..; basename -- "$PWD")
determines the parent directory name of the directory containing the file, which is the grandparent path from the file's perspective.
${old##*.}
is a Bash parameter expansion that returns the input filename's suffix (extension).
Note how {}
- the filename at hand - is passed as the 2nd argument to the command in order to bind to $1
, because bash -c
uses the 1st one to set $0
(which is set to dummy value _
here).
Note that each file is merely renamed, i.e., it stays in its original directory.
Caveat:
Upvotes: 3
Reputation: 1
Can't you use realpath ../../
or readlink -f ../../
? See this, readlink(1), realpath(3), canonicalize_file_name(3), and realpath(1). You may want to install the realpath
package on Debian or Ubuntu. Probably CentOS has an equivalent package. (readlink
should always be available, it is in GNU coreutils)
Upvotes: 1
Reputation: 11593
Assuming filepath doesn't end in /
, which it shouldn't if you use dirname
, you can do
Parent = "${filepath%/*}"
Grandparent = "${filepath%/*/*}"
So do something like this
[[ "${filepath%/*/*}" == "" ]] && echo "Path isn't long enough" || echo "${filepath%/*/*}"
Also this likely won't work if you're using relative paths (like find .
). In which case you will want to use
filepath=$(dirname "$f")
filepath=$(readlink -f "$filepath")
instead of
filepath=$(dirname "$f")
Also you're never stripping the extension, so there is no reason to get it from the file and then append it again.
Upvotes: 4
Reputation: 4069
Another method could be to use
(cd ../../; pwd)
If this were executed in any top-level paths such as /
, /usr/
, or /usr/share/
, you would get a valid directory of /
, but when you get one level deeper, you would start seeing results: /usr/share/man/
would return /usr
, /my/super/deep/path/is/awesome/
would return /my/super/deep/path
, and so on.
You could store this in a variable as well:
GRANDDADDY="$(cd ../../; pwd)"
and then use it for the rest of your script.
Upvotes: 9