10

I have a bunch of folders and I want to loop over all those folders and in each folder create another folder named "Photo"

F1
--F11
----F111
----F112
--F12

I want to have:

F1
--Photo
--F11
----Photo
----F111
------Photo
----F112
------Photo
--F12
----Photo
andrew.46
  • 37,085
  • 25
  • 149
  • 228
OOzy Pal
  • 233
  • 1
  • 7

5 Answers5

24

You can achieve it using a single find call.

find . -name 'Photo' -prune -o -type d -exec mkdir {}/Photo \;

The part -name 'Photo' -prune -o tells to not recurse to existing (and newly created) Photo directories: If the found file (or directory) has the name Photo, stop processing it (-prune). In the other case (-o), continue with directories (-type d) and execute (-exec) the command.

Melebius
  • 11,121
  • 8
  • 50
  • 77
  • ah nice, I tried it that way and I see from your answer where I was going wrong :) – Zanna Sep 14 '16 at 09:16
  • @Zanna `-prune`? :) Yes, it’s quite counter-intuitive but worth learning. – Melebius Sep 14 '16 at 09:22
  • Will it stop creating a folder 'Photo' if a file named 'Photo' exists? – Anwar Sep 14 '16 at 09:40
  • 3
    @Anwar The current version will. There is a grey zone in requirements. :) You can change this by specifying `-type d` together with `-name 'Photo'` before `-prune`. – Melebius Sep 14 '16 at 09:43
  • OP asked for that version I think. – Anwar Sep 14 '16 at 09:46
  • 2
    @Anwar At second glance, it won’t make sense. You cannot create a folder with the same name as a regular file within the same folder. And `find` will continue with other folders (including subfolders of that folder, if you are asking for that). – Melebius Sep 14 '16 at 10:31
10

You could pass find results to while instead of for since this way it won't break if the filenames have spaces

First cd to the directory F1, then do:

find -type d | while read dir ; do mkdir "$dir"/Photo ; done

If the directory exists, mkdir will just refuse to create it, so no problem there...

If your directory names could contain newlines, or other exotic strangeness, use this which can deal with arbitrary names:

find -type d -print0 | while IFS= read -r -d '' dir ; do mkdir "$dir"/Photo ; done

Thanks to @terdon for help with this :)

Zanna
  • 69,223
  • 56
  • 216
  • 327
  • If the op's `F1` directory is `~user/Pictures`, moving to the parent directory and running the line will put a `Photo` directory in every directory in his space, which could be thousands. Consider adding the `F1` as part of the commanline to avoid an easily made mistake. That way if it's inadvertently run from the wrong place, it won't create directories. – L. D. James Sep 14 '16 at 12:18
  • ah yeah I actually was unsure of their directory structure. I suggest doing it from `F1` instead, edited @L.D.James – Zanna Sep 14 '16 at 12:23
8

From the directory just above F1 run this script:

The purpose of the script is to avoid having redundant photo directories created under photo directories.

Also, it's design to prevent accidentally running it from a wrong location such as the user's root directory, whereas it wouldn't see the F1 directory and mistakenly create hundreds of photo directories in the wrong palaces.

Also, it was my intentions to make an easy to follow script so the user could customize it.

#!/bin/bash

while IFS= read -r -d '' i;
do
    if [[ ! -d $i/Photo ]]; then
        # Filter to skip folders named Photo
        thisfolder="$(echo $i | sed "s/.*\///")"
        if [[ ! $thisfolder == "Photo" ]]; then
            mkdir "$i/Photo"
        fi
    fi
done < <(find F1 -type d -print0)

Script Updated:

  • Changed the lower case p in photo to upper case.
  • Added support for non-stander filesnames (with suggestions from Terdon).
L. D. James
  • 24,768
  • 10
  • 68
  • 116
  • 1
    I'd definitely avoid [parsing ls](http://mywiki.wooledge.org/ParsingLs), it can fail in unusual ways. I think the answers that avoid this are probably safer. – Arronical Sep 14 '16 at 09:30
  • 3
    @Arronical `ls` isn't parsed. It's just feedback to the user... showing what is happening. Just like the words "This is a photo directory". It's letting the user know why it's not creating a directory there. – L. D. James Sep 14 '16 at 09:34
  • 1
    I had thought that might be the case, as I couldn't see anything being done with it. In that case, I retract my comment and slope off :) – Arronical Sep 14 '16 at 09:49
  • `find -type d F1`, maybe? – Olivier Grégoire Sep 14 '16 at 11:28
  • Note that this will fail if there's a *file* (and not a directory) called `$i/Photo`, but the OP hasn't specified what should be done in such a case. It will also fail in the, admittedly unlikely case, where one of the `$i`s contains a newline. – terdon Sep 14 '16 at 14:04
  • @terdon The script is design not to process files and folders named `Photo`. I originally had it output the case during the process to alert the OP that it's skipping those. But I removed the feedback an made it run quiet. Since errors are not trapped, it'll give feedback if there are errors. – L. D. James Sep 14 '16 at 14:14
  • I know, I am thinking of the edge case where there's a file called `Photo` in a subdir. Your script will skip that but then again, so will mine. As I said, there's no good way of dealing with that unless the OP tells us their preference. The issue with newlines is another matter. You can get around that by using the 2nd approach in [Zanna's answer](http://askubuntu.com/a/824730/85695), or, for bash, the much simpler one I give in [mine](http://askubuntu.com/a/824827/85695) . – terdon Sep 14 '16 at 14:18
  • @terdon I deleted one of the Photo folders and ran the script. It'll give the user an appropriate message for the user's attention. I could trap the error and give a more descriptive notice. I would consider it a very rare case to have a file by that name with no extension. But of course in the event, there will be an appropriate error message. Also, the user will recognize this when trying to put photos in a none directory. Thanks kindly for the critique! – L. D. James Sep 14 '16 at 14:22
  • @terdon Can you specify an example of a directly name that includes a newline? I would like to test it against the script. Thanks! – L. D. James Sep 14 '16 at 14:28
  • Sure: `mkdir 'this dir'$'\n''has a newline'`. By the way, files with no extension are the norm, not the exception. I very rarely use extensions when I create a file, for example. Files/dirs whose name contains a newline character are indeed rare, but you never know. – terdon Sep 14 '16 at 14:31
  • Let us [continue this discussion in chat](http://chat.stackexchange.com/rooms/45373/discussion-between-l-d-james-and-terdon). – L. D. James Sep 14 '16 at 15:17
  • Why not use `basename` or `${i##*/}` instead of `sed`? In any case the `$i` should be quoted in the `echo`. – Dennis Williamson Sep 14 '16 at 21:21
5

Python with os.walk can do it fairly easily and in only a few lines of code

$ mkdir dir_one dir_two
$ mkdir dir_two/dir_three
$ python -c 'import os;[os.mkdir(r + "/Pictures") for r,d,f in os.walk(".")]'                 
$ tree
.
├── dir_one
│   └── Pictures
├── dir_two
│   ├── dir_three
│   │   └── Pictures
│   └── Pictures
└── Pictures

Explanation

What we're dealing with is this command: python -c 'import os;[os.mkdir(r + "/Pictures") for r,d,f in os.walk(".")]'

Here we simply call python interpreter with -c flag to specify the command we give within single quotes. os.walk() function allows us to recursively step through all subdirectories, and for each subdirectory (represented by r ) , we create Pictures folder via os.mkdir() function. r+"/Pictures" is just a simple string manipulation to join subdirectory string and "/Pictures" string together to form one path

Replace "/Pictures" with "/Photo" to fit your requirements

Zanna
  • 69,223
  • 56
  • 216
  • 327
Sergiy Kolodyazhnyy
  • 103,293
  • 19
  • 273
  • 492
4

If you're using bash, you can simply run:

shopt -s globstar
for d in **/; do mkdir -p "$d"/Photo; done

The globstar option makes ** match "all files and 0 or more directories and subdirectories". In other words, it matches everything recursively. Because of the / at the end, **/ will match all directories and subdirectories (no files). Therefore, the $d will iterate over all directories in the current directory and their subdirectories and the Photos directory will be created in each of them.

The -p flag of mkdir means:

  -p, --parents
          no error if existing, make parent directories as needed

Here, I'm just using it to suppress error messages if the target directory already exists. Remove it if you want to see the errors.

Note that this will fail to create a Photo subdirectory in a target directory that contains a file named Photo. You haven't specified what should be done in those cases though and all other answers posted so far also have this limitation.

terdon
  • 98,183
  • 15
  • 197
  • 293