21

My directory structure is as follows

Directory1\file1.jpg
          \file2.jpg
          \file3.jpg

Directory2\anotherfile1.jpg
          \anotherfile2.jpg
          \anotherfile3.jpg

Directory3\yetanotherfile1.jpg
          \yetanotherfile2.jpg
          \yetanotherfile3.jpg

I'm trying to use the command line in a bash shell on ubuntu to take the first file from each directory and rename it to the directory name and move it up one level so it sits alongside the directory.

In the above example:

  • file1.jpg would be renamed to Directory1.jpg and placed alongside the folder Directory1

  • anotherfile1.jpg would be renamed to Directory2.jpg and placed alongside the folder Directory2

  • yetanotherfile1.jpg would be renamed to Directory3.jpg and placed alongside the folder Directory3

I've tried using:

find . -name "*.jpg"

but it does not list the files in sequential order (I need the first file).

This line:

find . -name "*.jpg" -type f -exec ls "{}" +;

lists the files in the correct order but how do I pick just the first file in each directory and move it up one level?

Any help would be appreciated!

Edit: When I refer to the first file what I mean is each jpg is numbered from 0 to however many files in that folder - for example: file1, file2...... file34, file35 etc... Another thing to mention is the format of the files is random, so the numbering might start at 0 or 1a or 1b etc...

user2008746
  • 211
  • 1
  • 2
  • 5
  • Expanding on Ignacio's comment, it's important to know what you mean by "first". First alphabetically? If so and if the numbers go to 2 digits, do you want to sort file3.jpg ahead of file10.jpg instead of the alphabetical way where file10.jpg would come first? If you don't want alphabetically, do you want the first in order of the way their stored on disk? By date? And so on. – Bdoserror Jan 24 '13 at 20:25

4 Answers4

36

You can go inside each dir and run:

$ mv `ls | head -n 1` ..
mirandes
  • 957
  • 10
  • 10
  • simple and Unixy, I like – Cyril Graze Apr 18 '17 at 11:19
  • 4
    @Cyril [it's a bad idea to pipe `ls` like that](https://unix.stackexchange.com/q/128985/44425) – phuclv Feb 19 '18 at 23:56
  • 1
    Hi @LưuVĩnhPhúc, thanks for the link. The article makes a good point. In my case however, the filenames were all auto-generated and following a very specific predefined format. So the edge cases the article warns about don't really apply. In general cases, yes, probably not a good idea. Thanks! – Cyril Graze Feb 21 '18 at 14:17
30

If first means whatever the shell glob finds first (lexical, but probably affected by LC_COLLATE), then this should work:

for dir in */; do
    for file in "$dir"*.jpg; do
        echo mv "$file" "${file%/*}.jpg" # If it does what you want, remove the echo
        break 1
    done
done

Proof of concept:

$ mkdir dir{1,2,3} && touch dir{1,2,3}/file{1,2,3}.jpg
$ for dir in */; do for file in "$dir"*.jpg; do echo mv "$file" "${file%/*}.jpg"; break 1; done; done
mv dir1/file1.jpg dir1.jpg
mv dir2/file1.jpg dir2.jpg
mv dir3/file1.jpg dir3.jpg
kojiro
  • 74,557
  • 19
  • 143
  • 201
2

Look for all first level directories, identify first file in this directory and then move it one level up

find . -type d \! -name . -prune | while read d; do
    f=$(ls $d | head -1)
    mv $d/$f .
done
Olaf Dietsche
  • 72,253
  • 8
  • 102
  • 198
0

Building on the top answer, here is a general use bash function that simply returns the first path that resolves to a file within the given directory:

getFirstFile() {
    for dir in "$1"; do
        for file in "$dir"*; do
            if [ -f "$file" ]; then
                echo "$file"
                break 1
            fi
        done
    done
}

Usage:

# don't forget the trailing slash
getFirstFile ~/documents/

NOTE: it will silently return nothing if you pass it an invalid path.

derpedy-doo
  • 1,097
  • 1
  • 18
  • 22