0

I've used StackExchange before and lurked on SuperUser but first time posting. Here's what I'm trying to do. (Pseudo)

For Each DIR containing $file *.Item
    Mkdir ./dir1/dir2
    Mv $file ./dir1/dir2/$file

Basically I have a folder structure full of files of type .Item, in every directory containing a file of type .Item I want to create 2 new sub directories call them dir1/dir2/ then I want to move all files of type .Item into those sub directories.

So far I've had luck doing some of the pieces of this but not all of it at once. I apologize if this has already been answered.

0

The main problem here is to avoid moving the files more than once. A naïve loops risks finding dir/x.Item, moving it to dir/dir1/dir2/x.Item, and then finding dir/dir1/dir2/x.Item and moving it to dir/dir1/dir2/dir1/dir2/x.Item, and then ...

Although I couldn't get Gnu find to do this in a couple of minutes of testing, I do note that Posix says "If a file is removed from or added to the directory hierarchy being searched it is unspecified whether or not find includes that file in its search." So one should be defensive.

So the easiest safe solution is to first construct the list of files, and then process the list.

If no .Item file has a newline character in its name, then it's easy to use find to construct the list. Assuming there are not too many files, this can be done directly into a bash array:

mapfile -t files < <(find . -name '*.Item')

Then it's straightforward to process the files:

for f in "${files[@]}"; do
  # make the target directory if it doesn't yet exist (-p)
  mkdir -p "$(dirname "$f")"/dir1/dir2
  # move the file
  mv "$f" "$(dirname "$f")/dir1/dir2
done

If there are a lot of files, we need to be more efficient and less cavalier about memory. We can construct a list of directories instead of a list of files (sort -u eliminates duplicates):

mapfile -t dirs < <(find . -name '*.Item' -printf %h | sort -u)

Now we can process the directories:

for d in "${dirs[@]}"; do
  mkdir -p "$d/dir1/dir2"
  mv "$d/*.Item" "$d/dir1/dir2"
done

Finally, if there were a lot of directories, we could use a temporary file instead of an array:

tmp=$(mktemp)
find . -name '*.Item' -printf %h | sort -u > "$tmp"
while IFS= read -r dir; do
  mkdir -p "$d/dir1/dir2"
  mv "$d/*.Item" "$d/dir1/dir2"
done < "$tmp"
rm "$tmp"
  • thanks for the detailed response. Unfortunately I wasn't able to get any of the above solutions to work out of the box, mostly because the mapfile command just hung on me when I ran it. There were only 210 files so I doubt that was the issue. I am on cygwin but I did verify that I had the mapfile command available. In the end your answer gave me the idea to use find . -iname '*.Item' > MapFile and then simply cat MapFile | while read f; do mkdir -p "$(dirname "$f")"/dir1/dir2 ; mv "$f" "$(dirname "$f")"/dir1/dir2 ; done but I am curious why I wasn't able to use mapfile! – Asterdahl Aug 11 '14 at 17:40
  • @DouglasMiller: My mistake; a couple of less-thans didn't make the transition into the answer. (It should be mapfile ... < <(...)). Corrected the answer, and my apologies. – rici Aug 11 '14 at 17:55

Your Answer

By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

Not the answer you're looking for? Browse other questions tagged or ask your own question.