Make xargs handle filenames that contain spaces

前端 未结 12 1129
北恋
北恋 2020-11-29 15:21
$ ls *mp3 | xargs mplayer  

Playing Lemon.  
File not found: \'Lemon\'  
Playing Tree.mp3.  
File not found: \'Tree.mp3\'  

Exiting... (End of file)  
相关标签:
12条回答
  • 2020-11-29 15:26

    ls | grep mp3 | sed -n "7p" | xargs -i mplayer {}

    Note that in the command above, xargs will call mplayer anew for each file. This may be undesirable for mplayer, but may be okay for other targets.

    0 讨论(0)
  • 2020-11-29 15:27

    Given the specific title of this post, here's my suggestion:

    ls | grep ' ' | tr ' ' '<' | sed 's|<|\\ |g'
    

    The idea is to convert blanks to any unique character, like '<', and then change that into '\ ', a backslash followed by a blank. You can then pipe that into any command you like, such as:

    ls | grep ' ' | tr ' ' '<' | sed 's|<|\\ |g' | xargs -L1 GetFileInfo
    

    The key here lies in the 'tr' and 'sed' commands; and you can use any character besides '<', such as '?' or even a tab-character.

    0 讨论(0)
  • 2020-11-29 15:28

    The xargs utility reads space, tab, newline and end-of-file delimited strings from the standard input and executes utility with the strings as arguments.

    You want to avoid using space as a delimiter. This can be done by changing the delimiter for xargs. According to the manual:

     -0      Change xargs to expect NUL (``\0'') characters as separators,
             instead of spaces and newlines.  This is expected to be used in
             concert with the -print0 function in find(1).
    

    Such as:

     find . -name "*.mp3" -print0 | xargs -0 mplayer
    

    To answer the question about playing the seventh mp3; it is simpler to run

     mplayer "$(ls *.mp3 | sed -n 7p)"
    
    0 讨论(0)
  • 2020-11-29 15:29

    Alternative solutions can be helpful...

    You can also add a null character to the end of your lines using Perl, then use the -0 option in xargs. Unlike the xargs -d '\n' (in approved answer) - this works everywhere, including OS X.

    For example, to recursively list (execute, move, etc.) MPEG3 files which may contain spaces or other funny characters - I'd use:

    find . | grep \.mp3 | perl -ne 'chop; print "$_\0"' | xargs -0  ls
    

    (Note: For filtering, I prefer the easier-to-remember "| grep" syntax to "find's" --name arguments.)

    0 讨论(0)
  • 2020-11-29 15:30

    It depends on (a) how attached you are to the number 7 as opposed to, say, Lemons, and (b) whether any of your file names contain newlines (and whether you're willing to rename them if they do).

    There are many ways to deal with it, but some of them are:

    mplayer Lemon*.mp3
    
    find . -name 'Lemon*.mp3' -exec mplayer {} ';'
    
    i=0
    for mp3 in *.mp3
    do
        i=$((i+1))
        [ $i = 7 ] && mplayer "$mp3"
    done
    
    for mp3 in *.mp3
    do
        case "$mp3" in
        (Lemon*) mplayer "$mp3";;
        esac
    done
    
    i=0
    find . -name *.mp3 |
    while read mp3
    do
        i=$((i+1))
        [ $i = 7 ] && mplayer "$mp3"
    done
    

    The read loop doesn't work if file names contain newlines; the others work correctly even with newlines in the names (let alone spaces). For my money, if you have file names containing a newline, you should rename the file without the newline. Using the double quotes around the file name is key to the loops working correctly.

    If you have GNU find and GNU xargs (or FreeBSD (*BSD?), or Mac OS X), you can also use the -print0 and -0 options, as in:

    find . -name 'Lemon*.mp3' -print0 | xargs -0 mplayer
    

    This works regardless of the contents of the name (the only two characters that cannot appear in a file name are slash and NUL, and the slash causes no problems in a file path, so using NUL as the name delimiter covers everything). However, if you need to filter out the first 6 entries, you need a program that handles 'lines' ended by NUL instead of newline...and I'm not sure there are any.

    The first is by far the simplest for the specific case on hand; however, it may not generalize to cover your other scenarios that you've not yet listed.

    0 讨论(0)
  • 2020-11-29 15:30

    I know that I'm not answering the xargs question directly but it's worth mentioning find's -exec option.

    Given the following file system:

    [root@localhost bokeh]# tree --charset assci bands
    bands
    |-- Dream\ Theater
    |-- King's\ X
    |-- Megadeth
    `-- Rush
    
    0 directories, 4 files
    

    The find command can be made to handle the space in Dream Theater and King's X. So, to find the drummers of each band using grep:

    [root@localhost]# find bands/ -type f -exec grep Drums {} +
    bands/Dream Theater:Drums:Mike Mangini
    bands/Rush:Drums: Neil Peart
    bands/King's X:Drums:Jerry Gaskill
    bands/Megadeth:Drums:Dirk Verbeuren
    

    In the -exec option {} stands for the filename including path. Note that you don't have to escape it or put it in quotes.

    The difference between -exec's terminators (+ and \;) is that + groups as many file names that it can onto one command line. Whereas \; will execute the command for each file name.

    So, find bands/ -type f -exec grep Drums {} + will result in:

    grep Drums "bands/Dream Theater" "bands/Rush" "bands/King's X" "bands/Megadeth"
    

    and find bands/ -type f -exec grep Drums {} \; will result in:

    grep Drums "bands/Dream Theater"
    grep Drums "bands/Rush"
    grep Drums "bands/King's X"
    grep Drums "bands/Megadeth"
    

    In the case of grep this has the side effect of either printing the filename or not.

    [root@localhost bokeh]# find bands/ -type f -exec grep Drums {} \;
    Drums:Mike Mangini
    Drums: Neil Peart
    Drums:Jerry Gaskill
    Drums:Dirk Verbeuren
    
    [root@localhost bokeh]# find bands/ -type f -exec grep Drums {} +
    bands/Dream Theater:Drums:Mike Mangini
    bands/Rush:Drums: Neil Peart
    bands/King's X:Drums:Jerry Gaskill
    bands/Megadeth:Drums:Dirk Verbeuren
    

    Of course, grep's options -h and -H will control whether or not the filename is printed regardless of how grep is called.


    xargs

    xargs can also control how man files are on the command line.

    xargs by default groups all the arguments onto one line. In order to do the same thing that -exec \; does use xargs -l. Note that the -t option tells xargs to print the command before executing it.

    [root@localhost bokeh]# find ./bands -type f  | xargs -d '\n' -l -t grep Drums
    grep Drums ./bands/Dream Theater 
    Drums:Mike Mangini
    grep Drums ./bands/Rush 
    Drums: Neil Peart
    grep Drums ./bands/King's X 
    Drums:Jerry Gaskill
    grep Drums ./bands/Megadeth 
    Drums:Dirk Verbeuren
    

    See that the -l option tells xargs to execute grep for every filename.

    Versus the default (i.e. no -l option):

    [root@localhost bokeh]# find ./bands -type f  | xargs -d '\n'  -t grep Drums
    grep Drums ./bands/Dream Theater ./bands/Rush ./bands/King's X ./bands/Megadeth 
    ./bands/Dream Theater:Drums:Mike Mangini
    ./bands/Rush:Drums: Neil Peart
    ./bands/King's X:Drums:Jerry Gaskill
    ./bands/Megadeth:Drums:Dirk Verbeuren
    

    xargs has better control on how many files can be on the command line. Give the -l option the max number of files per command.

    [root@localhost bokeh]# find ./bands -type f  | xargs -d '\n'  -l2 -t grep Drums
    grep Drums ./bands/Dream Theater ./bands/Rush 
    ./bands/Dream Theater:Drums:Mike Mangini
    ./bands/Rush:Drums: Neil Peart
    grep Drums ./bands/King's X ./bands/Megadeth 
    ./bands/King's X:Drums:Jerry Gaskill
    ./bands/Megadeth:Drums:Dirk Verbeuren
    [root@localhost bokeh]# 
    

    See that grep was executed with two filenames because of -l2.

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