How can I use inverse or negative wildcards when pattern matching in a unix/linux shell?

前端 未结 11 1601
梦如初夏
梦如初夏 2020-11-22 13:50

Say I want to copy the contents of a directory excluding files and folders whose names contain the word \'Music\'.

cp [exclude-matches] *Music* /target_direc         


        
相关标签:
11条回答
  • 2020-11-22 14:22

    Not in bash (that I know of), but:

    cp `ls | grep -v Music` /target_directory
    

    I know this is not exactly what you were looking for, but it will solve your example.

    0 讨论(0)
  • 2020-11-22 14:22

    One solution for this can be found with find.

    $ mkdir foo bar
    $ touch foo/a.txt foo/Music.txt
    $ find foo -type f ! -name '*Music*' -exec cp {} bar \;
    $ ls bar
    a.txt
    

    Find has quite a few options, you can get pretty specific on what you include and exclude.

    Edit: Adam in the comments noted that this is recursive. find options mindepth and maxdepth can be useful in controlling this.

    0 讨论(0)
  • 2020-11-22 14:24

    The following works lists all *.txt files in the current dir, except those that begin with a number.

    This works in bash, dash, zsh and all other POSIX compatible shells.

    for FILE in /some/dir/*.txt; do    # for each *.txt file
        case "${FILE##*/}" in          #   if file basename...
            [0-9]*) continue ;;        #   starts with digit: skip
        esac
        ## otherwise, do stuff with $FILE here
    done
    
    1. In line one the pattern /some/dir/*.txt will cause the for loop to iterate over all files in /some/dir whose name end with .txt.

    2. In line two a case statement is used to weed out undesired files. – The ${FILE##*/} expression strips off any leading dir name component from the filename (here /some/dir/) so that patters can match against only the basename of the file. (If you're only weeding out filenames based on suffixes, you can shorten this to $FILE instead.)

    3. In line three, all files matching the case pattern [0-9]*) line will be skipped (the continue statement jumps to the next iteration of the for loop). – If you want to you can do something more interesting here, e.g. like skipping all files which do not start with a letter (a–z) using [!a-z]*, or you could use multiple patterns to skip several kinds of filenames e.g. [0-9]*|*.bak to skip files both .bak files, and files which does not start with a number.

    0 讨论(0)
  • 2020-11-22 14:25

    If you want to avoid the mem cost of using the exec command, I believe you can do better with xargs. I think the following is a more efficient alternative to

    find foo -type f ! -name '*Music*' -exec cp {} bar \; # new proc for each exec
    
    
    
    find . -maxdepth 1 -name '*Music*' -prune -o -print0 | xargs -0 -i cp {} dest/
    
    0 讨论(0)
  • 2020-11-22 14:32

    In Bash you can do it by enabling the extglob option, like this (replace ls with cp and add the target directory, of course)

    ~/foobar> shopt extglob
    extglob        off
    ~/foobar> ls
    abar  afoo  bbar  bfoo
    ~/foobar> ls !(b*)
    -bash: !: event not found
    ~/foobar> shopt -s extglob  # Enables extglob
    ~/foobar> ls !(b*)
    abar  afoo
    ~/foobar> ls !(a*)
    bbar  bfoo
    ~/foobar> ls !(*foo)
    abar  bbar
    

    You can later disable extglob with

    shopt -u extglob
    
    0 讨论(0)
提交回复
热议问题