Command-line mv exclusion list

command linerenamescp

Is there a way to mv,cp, or any file operation such that I could specify all the files I don't want affected?

For example, say I have a folder with the files file1, file2, and file3, and I want to move file1 and file2 somewhere. Rather than explicitly naming the files to move (mv file1 file2 /path/to/destination), I want to name the files not to move and have all the others in the folder get moved (mv --some-switch file3 /path/to/destination)

Best Answer

  • You can use the advanced globbing patterns in some shells to match all the files in a directory except for those matching a particular pattern. For example, in ksh, bash or zsh, the command

    shopt -s extglob   ## needed in bash only
    setopt ksh_glob   ## needed in zsh only
    mv /source/!(*.bak) /destination
    

    will move all files in /source to /destination except for the files matching *.bak. In zsh, you can also write /source/^*.bak if you first run setopt extended_glob, and more generally (again requiring setopt extended_glob) /source/*~*.bak (or /source/a*~*.bak for all files whose name begins with a except for .bak files, etc).

  • Zsh has a mass copy/move/link command that can be used, amongst others, to move all files except for those matching a pattern. For example, the following command moves all files except *.bak from /source to target, and adds .bak to their name in the process:

    autoload zmv
    zmv '/source/(*)~*.bak' '/target/$1.bak'
    
  • There are several commands called rename floating around. On Debian and Ubuntu, /usr/bin/rename is a perl script that moves files to a new name generated by a perl expression. You can exclude files from renaming by not generating a new name if the file is to be excluded. For example, the following command (using this particular rename program) moves all files except *.bak from /source to /target:

    rename 's!/source!/target! unless m!\.bak$!' /source/*
    
  • You can use the find command to select the files you want to move. For example, the following command moves all regular files except *.bak in /source or a subdirectory into /target (note that the directory structure is collapsed):

    find /source -type f \! -name '*.bak' -exec mv {} /target/ \;
    

    or (more efficient if there are many files to move)

    find /source -type f \! -name '*.bak' -exec sh -c 'mv "$@" "$0"' /target/ {} +
    
  • rsync is a generalization of cp and scp with very powerful include/exclude rules. For example, the following command copies all files except *.bak in /source or a subdirectory into /target, respecting the directory structure:

    rsync -a --exclude '*.bak' /source/ /target/
    
  • pax is (amongst other things) another cp on steroids. Its exclusion rules are not nearly as powerful as rsync's, but it has the additional ability to rename files as they are copied. If you rename a file to the empty string, it's excluded from the copy. For example, the following command copies all files except *.bak in /source or a subdirectory into /target, and renames the files to .bak in passing.

    cd /source && pax -rw -pp -s '/.*\.bak$//' -s '/$/.bak/' . /target/
    

    The example above has the unfortunate side effect of creating directories called foo.bak, which can be avoided by combining find with pax:

    { cd /source && find . -type f; } | \
    pax -rw -pp -s '/.*\.bak$//' -s '/$/.bak/' /target/
    
Related Question