Take the 2-minute tour ×
Unix & Linux Stack Exchange is a question and answer site for users of Linux, FreeBSD and other Un*x-like operating systems.. It's 100% free, no registration required.

I am trying to organise the album art in my music collection so that only one image is assigned to each folder.

My directory structure currently looks like:

/path/to/music/Album Name/
/path/to/music/Album Name/1 - Track one.flac
/path/to/music/Album Name/cover.jpg (either this)
/path/to/music/Album Name/folder.jpg (or this)
/path/to/music/Album Name/Album Name.jpg (or this is the largest file)
/path/to/music/Album Name/AlbumArtSmall.jpg  

(plus other low resolution images generated by Windows media player)

I would like to scan through each folder and delete all but the largest jpg and rename it to cover.jpg.

As the tags indicate, I have cygwin installed, but can also boot into Ubuntu where I have access to bash and zsh, if this makes the problem easier.

share|improve this question
Do you mean the largest by file size in bytes or the largest by picture size in pixels (that isn't always the same)? –  rush Mar 18 '13 at 19:50
Ideally by pixels, but I'm willing to go with bytes if the former is too difficult –  jClark94 Mar 18 '13 at 20:01
Does cygwin have imagemagick available? If it does you could identify the largest by pixels reasonably easily. –  tink Mar 18 '13 at 20:21
I ended up marking the current solution as accepted, with the recommended test for existence, as, even though the other solution worked for a directory, it did not work from the top level. My thanks to everyone involved. –  jClark94 Mar 20 '13 at 18:17
add comment

3 Answers

up vote 4 down vote accepted

In zsh (which you can use from Cygwin or Linux), you can use glob qualifiers to pick the largest file. That's the largest file by byte size, not in terms of image dimensions ­— which is probably the right thing here since it privileges high-resolution images.

for d in /path/to/music/**/*(/); do
  rm -f $d/*.jpg(oL[1,-2]N)
  mv $d/*.jpg $d/cover.jpg

The loop traverses all the subdirectories of /path/to/music recursively. The (/) suffix restricts the matches to directories. The argument to rm -f use three glob qualifiers: oL to sort by size; [1,-2] to retain only the matches up to the next-to-last one (PATTERN([-1]) is the last match, PATTERN([-2]) is the next-to-last match, and PATTERN([1,-2]) is the list of matches from the first to the next-to-last inclusive); and N to produce an empty list rather than leave the pattern unexpanded or report an error if the pattern matches no file.

You may get harmless error if the remaining file is already called cover.jpg or if there is no .jpg file in a directory. To avoid them, change the mv call to

[[ -e $d/cover.jpg ]] || mv $d/*.jpg $d/cover.jpg

Here's an alternative method that renames first then deletes. It uses the PATTERN1~PATTERN2 syntax, which requires the extended_glob option, to select files that match PATTERN1 but not PATTERN2. ((#jpgs)) tests if the jpgs array contains at least one element.

setopt extended_glob
for d in /path/to/music/**/*(/); do
  ((#jpgs)) || continue
  [[ $jpgs[1] == */cover.jpg ]] || mv $jpgs[1] $d/cover.jpg
  rm -f $jpgs[2,-1]
share|improve this answer
add comment

Here's a Schwartzian transform for you:

stat -c "%s %n" *.jpg | sort -n | cut -d " " -f 2- | head -n -1 |
while IFS= read -r filename; do echo rm "$filename"; done

Use stat to output the size and filename, sort, remove the size field, ignore the largest one, and iterate over the resulting files.

share|improve this answer
add comment

Using GNU find.

#!/usr/bin/env bash
# GNU find + bash4 / ksh93v / zsh
# Get the largest file matching pattern in the given directories recursively
${ZSH_VERSION+false} || emulate ksh
${BASH_VERSION+shopt -s lastpipe extglob}

function getLargest {
    typeset -A cur top || return
    typeset dir x
    for dir in "$2"/*/; do
        [[ -d $dir ]] || return 0
        getLargest "$1" "${dir%/}" || return
        find "$dir" -maxdepth 1 -type f -name "$1" -printf '%s\0%f\0' | {
            while :; do
                for x in cur\[{size,name}\]; do
                    IFS= read -rd '' "$x" || break 2
                if (( cur[size] > top[size] )); then
                    top[size]=${cur[size]} top[name]=${cur[name]}
            mv -- "${dir}"{"${top[name]}",cover.jpg}
            rm -f -- "${dir}"!(cover.jpg)

# main pattern dir [ dir ... ]
function main {
if [[ -n $1 ]]; then
    typeset dir pattern=$1
    for dir; do
        [[ -d $dir ]] || return
            getLargest "$pattern" "$dir"
        return 1

main \*.jpg /path/to/music/
share|improve this answer
add comment

Your Answer


By posting your answer, you agree to the privacy policy and terms of service.

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