5

Let's say that I have the following directory structure where the name of the directories does not matter:

currentDir/
   Dir1
      Dir1
         Dir1 *
      Dir2 *
      Dir3 *
   Dir2
      Dir1 *
      Dir2 
         Dir1*
   Dir3*

Is there a way that I can create a directory named "Texture" in only the directories at the end of the tree branch (in the example they are marked with an *). I would like to make it so that only those directories get a Texture directory but not any others.

All directories may or may not have files, but the end directories will not have any other directories in them.

6

First turn on recursive globbing

shopt -s globstar

Now we can look in all directories with **. To unset, you can use shopt -u globstar (it's off by default, so it will be off when you open a new shell).

In all of the below, remove echo after testing to actually create the directories.

If the end-of-tree directories are empty, you could test for emptiness and make the directory only if it is detected... something like

for d in **/; do [[ -z "$(ls -Aq "$d")" ]] && echo mkdir "$d"Texture; done

i.e. if the output of ls -A (hidden files except current and parent dir) -q (printing ? for non-printing characters) is empty, make a directory.

But since the directories contain files, we'd better test that there are no directories

for d in **/; do [[ -z "$(find "$d" -maxdepth 1 -mindepth 1 -type d)" ]] && echo mkdir "$d"Texture; done

mindepth -1 is to stop the current directory being found... find sees hidden files, so we know those are included. Since any character should pass the test (we only want to check the output is empty), this should be fairly safe...

A more concise way (sorry Greg) (but actually all these methods do something similarly iffy - we can just about get away with using filenames as text since we aren't actually trying to do anything with those files themselves here):

for d in **/; do (ls -Alq "$d" | grep -q '^d') || echo mkdir "$d"Textures; done

here we add the -l flag for long output, which prints every file in the form

drwxrwxr-x 5 zanna zanna 4096 Sep 27 22:00 Dir1

we then gulp pipe that stuff into grep to see if any of the lines starts with d (meaning there's a directory). We don't want grep to print any of that meaningless unreliable text, so we just check its exit status with -q to see if it found anything. If it didn't, we make a directory (using the || or operator, meaning, do the next command if the last one failed). This should work, actually, even if your filenames contain horrible characters such as newlines, since ls prints them as ? - every line should start with a letter indicating the file type, so, probably, no kittens will die.

  • You might want to restrict the for loop to iterate over directories: for d in **/; and don't forget to quote your variables – glenn jackman Sep 26 '17 at 19:58
  • @glennjackman thanks a lot! It really breaks if not restricted to directories. And I thought I could quote the whole expression, but, nope! – Zanna Sep 26 '17 at 20:09
  • 2
    The $() introduces a subshell, so the nested double quotes don't interact with each other at all. – glenn jackman Sep 26 '17 at 20:37
  • Would it be possible to modify this to create the textures folder one level above the last folder so for in the example it would make currentDir/Dir1/Textures, currentDir/Dir1/Di1/Textures, currentDir/Dir2/, currentDir/Dir2/Dir2, and currentDir/ – kingcobra1986 Sep 27 '17 at 19:41
  • I don't understand your example - I think one level above the last should give currentDir/Dir1/Dir1/Textures, currentDir/Dir1/Dir2/Textures and currentDir/Dir2/Textures @kingcobra1986 also I couldn't figure out a way yet. Python can do it I assume... – Zanna Sep 27 '17 at 20:36
5

Here's a painful find -exec call

Before:

$ tree currentDir
currentDir
├── Dir1
│   ├── Dir1
│   │   └── Dir1
│   ├── Dir2
│   └── Dir3
├── Dir2
│   ├── Dir1
│   └── Dir2
│       └── Dir1
└── Dir3

Create the Texture directories

$ find currentDir -depth -type d -exec bash -c 'cd "$1"; shopt -s globstar nullglob; texture=(**/Texture); [[ ${#texture[@]} -eq 0 ]] && mkdir Texture' _ '{}' \;

That relies on:

  • find's -depth option to enable depth-first navigation
  • bash's globstar shell option to enable recursive file pattern matching
  • note the _ argument: that goes into the bash var $0 so that find's current pathname can be bash var $1

After

$ tree currentDir
currentDir
├── Dir1
│   ├── Dir1
│   │   └── Dir1
│   │       └── Texture
│   ├── Dir2
│   │   └── Texture
│   └── Dir3
│       └── Texture
├── Dir2
│   ├── Dir1
│   │   └── Texture
│   └── Dir2
│       └── Dir1
│           └── Texture
└── Dir3
    └── Texture
  • Outstanding use of find command, nice :-) – George Udosen Sep 26 '17 at 19:46
  • 1
    @glenn did you mean to VTC this as off topic? Seems fine to me, what am I missing? Also, why not mkdir -p instead of testing to see if it already exists? – terdon Sep 27 '17 at 9:11
  • Yes, this is not Ubuntu-specific. I think this is better off on SO. How would you use mkdir -p to only add the subdir at a leaf directory without testing? – glenn jackman Sep 27 '17 at 9:38
  • Would it be possible to modify this to create the textures folder one level above the last folder so for in the example it would make currentDir/Dir1/Textures, currentDir/Dir1/Di1/Textures, currentDir/Dir2/, currentDir/Dir2/Dir2, and currentDir/ – kingcobra1986 Sep 27 '17 at 19:41
3

This should work:

shopt -s globstar; for i in **/; do sub=$(find "$i" -type d | wc -l); if [[ "$sub" -eq 1 ]]; then mkdir "$i"/Texture; fi;done

Result:

currentDir
├── Dir1
│   ├── Dir1
│   │   └── Dir1
│   │       └── Texture
│   ├── Dir2
│   │   └── Texture
│   └── Dir3
│       └── Texture
├── Dir2
│   ├── Dir1
│   │   └── Texture
│   └── Dir2
│       └── Dir1
│           └── Texture
└── Dir3
    └── Texture
  • This only add a Texture file to currentDir/Dir3 – kingcobra1986 Sep 26 '17 at 19:25
1

Python makes this easy.

python3 -c 'import os
for d in [root for root, dirs, files in os.walk(".") if not any(dirs)]:
    os.mkdir(d + "/Texture")'

You can run that command from your shell. It passes a three-line script to the Python 3 interpreter, which runs it. The script builds a list of all the directories under . (the one you're in), then creates a Texture subdirectory in each one that did not already have any subdirectories.

This is simpler to write and easier to understand than most other methods, because Python has an os.walk function that enumerates directories in a way that makes it easy to see which ones have subdirectories (and more generally because Python is a language many users find clean and intuitive).

If you want to just show what new directories would be created instead of actually creating them, replace os.mkdir with print. Here's that, in case you want to copy and paste:

python3 -c 'import os
for d in [root for root, dirs, files in os.walk(".") if not any(dirs)]:
    print(d + "/Texture")'

There are ways to write either of these three-line commands on a single line, but there's no need. You can enter the command you want to use on an interactive shell prompt by typing or pasting it in, or you can use it in a shell script. When entered interactively, your shell will print > (unless you've set $PS2 to something different) at the beginning of the second and third lines. That's not a problem.

The foregoing assumes you're using a Bourne-style shell like bash, dash/sh, ksh, zsh, posh, etc. (Those three answers use shopt -s globstar. That command is Bash-specific, although some other shells have a similar feature. So if they work for you, this should too.) If you're using Ubuntu and you don't know what shell you're typing commands into, it's bash. If you're using csh or tcsh, you will have to place a \ at the ends of the first two lines to avoid an Unmatched '. error, and you'll see ? instead of >.

To test this method on an existing directory hierarchy without creating the directories, just use the second command as described above. But if you like, you can make a new directory hierarchy somewhere just for testing purposes. mkdir -p dir/{a/{b/c,d,e},f/{g,h/i},j} creates a directory tree with the same topology as the one in your example; find dir -type d -exec touch {}/x \; populates it with some files so you can check that the presence of non-directories isn't a problem; cd dir goes into it; and then you can test the commands shown above, as well as any other method you may be interested in, such as those presented in the other answers; then run tree.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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