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

前端 未结 11 1593
梦如初夏
梦如初夏 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:07

    My personal preference is to use grep and the while command. This allows one to write powerful yet readable scripts ensuring that you end up doing exactly what you want. Plus by using an echo command you can perform a dry run before carrying out the actual operation. For example:

    ls | grep -v "Music" | while read filename
    do
    echo $filename
    done
    

    will print out the files that you will end up copying. If the list is correct the next step is to simply replace the echo command with the copy command as follows:

    ls | grep -v "Music" | while read filename
    do
    cp "$filename" /target_directory
    done
    
    0 讨论(0)
  • 2020-11-22 14:10

    A trick I haven't seen on here yet that doesn't use extglob, find, or grep is to treat two file lists as sets and "diff" them using comm:

    comm -23 <(ls) <(ls *Music*)
    

    comm is preferable over diff because it doesn't have extra cruft.

    This returns all elements of set 1, ls, that are not also in set 2, ls *Music*. This requires both sets to be in sorted order to work properly. No problem for ls and glob expansion, but if you're using something like find, be sure to invoke sort.

    comm -23 <(find . | sort) <(find . | grep -i '.jpg' | sort)
    

    Potentially useful.

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

    You can also use a pretty simple for loop:

    for f in `find . -not -name "*Music*"`
    do
        cp $f /target/dir
    done
    
    0 讨论(0)
  • 2020-11-22 14:13

    The extglob shell option gives you more powerful pattern matching in the command line.

    You turn it on with shopt -s extglob, and turn it off with shopt -u extglob.

    In your example, you would initially do:

    $ shopt -s extglob
    $ cp !(*Music*) /target_directory
    

    The full available extended globbing operators are (excerpt from man bash):

    If the extglob shell option is enabled using the shopt builtin, several extended pattern matching operators are recognized.A pattern-list is a list of one or more patterns separated by a |. Composite patterns may be formed using one or more of the following sub-patterns:

    • ?(pattern-list)
      Matches zero or one occurrence of the given patterns
    • *(pattern-list)
      Matches zero or more occurrences of the given patterns
    • +(pattern-list)
      Matches one or more occurrences of the given patterns
    • @(pattern-list)
      Matches one of the given patterns
    • !(pattern-list)
      Matches anything except one of the given patterns

    So, for example, if you wanted to list all the files in the current directory that are not .c or .h files, you would do:

    $ ls -d !(*@(.c|.h))
    

    Of course, normal shell globing works, so the last example could also be written as:

    $ ls -d !(*.[ch])
    
    0 讨论(0)
  • 2020-11-22 14:15

    this would do it excluding exactly 'Music'

    cp -a ^'Music' /target
    

    this and that for excluding things like Music?* or *?Music

    cp -a ^\*?'complete' /target
    cp -a ^'complete'?\* /target
    
    0 讨论(0)
  • 2020-11-22 14:19

    In bash, an alternative to shopt -s extglob is the GLOBIGNORE variable. It's not really better, but I find it easier to remember.

    An example that may be what the original poster wanted:

    GLOBIGNORE="*techno*"; cp *Music* /only_good_music/
    

    When done, unset GLOBIGNORE to be able to rm *techno* in the source directory.

    0 讨论(0)
提交回复
热议问题