1

I have multiple layered sub-directories, and I am trying to relocate all directories which contain files of a pattern into a new parent directory. I want to maintain the contents of the directories I wish to move, whether or not they have files in addition to those matching a pattern.

For example:

homedir/subdir/{file.txt, file.png, file.rtf}
homedir/subdir/{file.txt, file.png}
homedir/subdir/{file.txt, file.jpg}
homedir/subdir/subdir/{file.png, file.png, file.mp3}

I want each DIRECTORY containing "*.png" (along with any additional non-png contents that may be within the directory) to be moved to /dirPNG

So, the result would be:

homedir/subdir/{file.txt, file.jpg}
homedir/dirPNG/subdir/{file.txt, file.png, file.rtf}
homedir/dirPNG/subdir/{file.txt, file.png}
homedir/dirPNG/subdir/subdir/{file.png, file.png, file.mp3}
thanasisp
  • 8,122

2 Answers2

1

You can use a nested find. This version requires GNU find or similar that supports -maxdepth and -quit, but there are workarounds available for POSIX compatibility.

find homedir -depth -type d \
    -exec sh -c '[ -n "$(find "$@" -maxdepth 1 -type f -name "*.png" -print -quit)" ]' _ {} \; \
    -exec sh -c 'echo "Move $@"' _ {} \;

Replace or supplement echo "Move $@" with mv "$@" /dirPNG when you are absolutely sure the code is doing what you want.

It works by traversing directories depth first, searching for any files matching *.png in each directory. If there's a match the directory is moved.

As a result, if you have homedir/subdir containing subsubdir/a.png and also b.png, you'll get subsubdir moved before subdir, so they become peers in the target directory rather than hierarchical.

If this does not achieve your required result you can try removing -depth, but you will receive a large number of find: ‘subdir’: No such file or directory type errors during the traversal where find tries to descend into a directory that's already been moved. This isn't fatal but it's inelegant.

Regardless, you will get errors if you try to move a directory into the destination if there's a directory with the same name already present. You have not specified what should happen in this instance so an error and a refusal to move will have to suffice.

Chris Davies
  • 116,213
  • 16
  • 160
  • 287
0

Here is the script that will do what you need, but be warned that what you are doing is dangerous if applied to your HOME directory.

The below script creates a batch file to perform the actions.

That allows you to review the intended actions before you actually perform the task, so that you may delete any lines that could spell disaster for you.

There are other comments in the script which I suggest you consider for future expanded functionality.

#!/bin/sh

BASE=basename "$0" ".sh" TMP="/tmp/tmp.$$.${BASE}" ; rm -f ${TMP}

START=pwd BATCH="${START}/${BASE}.batch"

MODE=0 INSENS=0 ONE_PARTITION="" while [ $# -gt 0 ] do case "${1}" in "--suffix" ) MODE=1 ; STRNG="${2}" ; pattern_ident="RELOC_${STRNG}" ; shift ; shift ;; "--prefix" ) MODE=2 ; STRNG="${2}" ; pattern_ident="RELOC_${STRNG}" ; shift ; shift ;; "--single" ) ONE_PARTITION="-xdev" ; shift ;; "--insensitive" ) INSENS=1 ; shift ;; * ) echo "\n\t ERROR: Invalid option used on command line. Options allowed: [ --suffix | --prefix ] \n Bye!\n" ; exit 1 ; ;; esac done

if [ ${MODE} -eq 0 -o -z "${STRNG}" ] ; then echo "\n\t ERROR: Must specify one of --suffix or --prefix values on the command line.\n Bye!\n" ; exit 1 ; fi

SEARCH_ROOT="${HOME}" RELOCN_DIR="${HOME}/${pattern_ident}"

if [ ! -d "${RELOCN_DIR}" ] then mkdir "${RELOCN_DIR}" if [ $? -ne 0 ] ; then echo "\n\t ERROR: Unable to create target directory for directory relocation actions.\n Bye!\n" ; exit 1 ; fi

fi 2>&1 | awk '{ printf("\t %s\n", $0 ) ; }'

Ignore start directory itself

Handling directories with spaces/characters in their name

rm -f "${TMP}.search"*

Segregate dot dirs from others

find "${SEARCH_ROOT}" -mindepth 1 -maxdepth 1 -type d -print | sort >"${TMP}.search.raw" awk -F / '{ if( index( $NF, "." ) == 1 ) { print $0 } ; }' <"${TMP}.search.raw" >"${TMP}.search" awk -F / '{ if( index( $NF, "." ) == 0 ) { print $0 } ; }' <"${TMP}.search.raw" >>"${TMP}.search"

########## #more "${TMP}.search" #exit 0 ##########

while read SEARCH_dir do if [ -z "${SEARCH_dir}" ] ; then break ; fi

echo &quot;\t Scanning: ${SEARCH_dir} ...&quot; &gt;&amp;2

### insert function to dynamically remap ${PAT} to expanded set for case insensitive
if [ ${INSENS} -eq 1 ]
then
    sPAT=&quot;[Pp][Nn][Gg]&quot;
else
    sPAT=&quot;${STRNG}&quot;
fi

########## #echo "sPAT = ${sPAT}" >&2 #exit 0 ##########

case ${MODE} in
    1)  

######### #( eval find &quot;${SEARCH_dir}&quot; ${ONE_PARTITION} -type f -name '*.${sPAT}' -print | awk -F'/[^/]*$' '{print $1}' | more >&2 ) <&2 #exit 0 #########

        eval find \&quot;${SEARCH_dir}\&quot; ${ONE_PARTITION} -type f -name \'\*\.${sPAT}\' -print |
            awk -F'/[^/]*$' '{print $1}' | sort | uniq
        ;;
    2)  
        eval find \&quot;${SEARCH_dir}\&quot; ${ONE_PARTITION} -type f -name \'${sPAT}\*\' -print |
            awk -F'/[^/]*$' '{print $1}' | sort | uniq
        ;;
esac

done <"${TMP}.search" >"${TMP}.dirsToMove" if [ ! -s "${TMP}.dirsToMove" ] ; then echo "\n\t No directories identified for specified pattern. No action taken.\n Bye!\n" ; exit 0 ; fi

echo "\n Starting directory move ..."

######### #more "${TMP}.dirsToMove" #exit 0 ########

Doing the action direction without a specific visual review could corrupt

the HOME directory to point of unusability if the wrong directories are acted upon.

Save all action commands into a batch file for manual visual review

to confirm sanity of actions identified before applying.

rm -f "${BATCH}"

while read DirToMove do if [ -n "${DirToMove}" ] then new=echo &quot;${DirToMove}&quot; | eval sed \'s\+${SEARCH_ROOT}/\+\+\' echo "mv -fv &quot;${DirToMove}&quot; &quot;${RELOCN_DIR}/${new}&quot; " 2>&1 | awk '{ printf("%s\n", $0 ) ; }' >>"${BATCH}" fi done <"${TMP}.dirsToMove" if [ ! -s "${BATCH}" ] ; then echo "\n\t No directories identified for specified pattern. No action taken.\n Bye!\n" ; exit 0 ; fi

echo "\n Directory move BATCH file was created:\n" cat "${BATCH}" | awk '{ printf("\t %s\n", $0 ) ; }' echo "" wc -l "${BATCH}"

exit 0 exit 0 exit 0