2012-05-29 15 views
31

Quando sto usando xargs a volte non ho bisogno di utilizzare in modo esplicito la stringa sostituzione:Modifica sostituire stringa nella xargs

find . -name "*.txt" | xargs rm -rf 

In altri casi, voglio specificare la stringa di sostituzione, al fine di fare le cose come:

find . -name "*.txt" | xargs -I '{}' mv '{}' /foo/'{}'.bar 

il comando precedente dovrebbe spostare tutti i file di testo nella directory corrente in /foo e aggiungerà l'estensione bar a tutti i file.

Se invece di aggiungere del testo alla stringa di sostituzione, volevo modificare quella stringa in modo da poter inserire del testo tra il nome e l'estensione dei file, come potrei farlo? Ad esempio, supponiamo di voler fare lo stesso dell'esempio precedente, ma i file devono essere rinominati/spostati da <name>.txt a /foo/<name>.bar.txt (anziché /foo/<name>.txt.bar).

UPDATE: riesco a trovare una soluzione:

find . -name "*.txt" | xargs -I{} \ 
    sh -c 'base=$(basename $1) ; name=${base%.*} ; ext=${base##*.} ; \ 
      mv "$1" "foo/${name}.bar.${ext}"' -- {} 

ma mi chiedo se c'è un/soluzione migliore più breve.

+1

No, tranne che avrei usato più citando "$ 1" "/ $ {nome} .bar foo' mv $ {ext}"' e si potrebbe fare '. basename' come questo: 'base = $ {1 ## * /}'. Dovresti pubblicare la tua soluzione come risposta e accettarla. –

+0

@DennisWilliamson Grazie per il tuo commento! Aspetterò un po 'di più, solo per vedere se qualcuno si inventerà qualcosa di stravagante, altrimenti risponderò io stesso alla domanda. – betabandido

+1

Penso che se il tuo nome file è l'ultima cosa sulla linea, non è necessario -I {} né {} nella riga di comando. (Si noti che il vero scopo di xargs è raggruppare però, quindi se NON volete più cose alla fine della chiamata di 1 xargs avete bisogno di "-l 1" (o -L 1 per alcune versioni di xargs). -I {} implica -l 1, ecco perché funziona anche qui.) –

risposta

12

In casi come questo, un ciclo while sarebbe più leggibile:

find . -name "*.txt" | while IFS= read -r pathname; do 
    base=$(basename "$pathname"); name=${base%.*}; ext=${base##*.} 
    mv "$pathname" "foo/${name}.bar.${ext}" 
done 

Si noti che è possibile trovare i file con lo stesso nome in differenti sottodirectory. Stai bene con i duplicati sovrascritti da mv?

+0

Sì, lo so che ha quel "problema". In realtà l'esempio che ho postato è piuttosto sciocco. L'ho appena inventato per mostrare un semplice esempio nella mia domanda, quindi nessun problema :) – betabandido

+1

Per gestire i nomi di file con una nuova riga, usa questo: 'find. -name "* .txt" -print0 | mentre leggi -r -d "pathname; do ... ' – Cem

+2

Si noti che questo approccio (seriale) non funzionerà se si sta tentando di parallelizzare xargs usando' --max-procs'. – g33kz0r

2

Se ti è permesso usare qualcosa di diverso da bash/sh, E questo è solo per un "mv" di fantasia ... potresti provare il venerabile script "rename.pl". Lo uso su Linux e cygwin su Windows tutto il tempo.

http://people.sc.fsu.edu/~jburkardt/pl_src/rename/rename.html

rename.pl 's/^(.*?)\.(.*)$/\1-new_stuff_here.\2/' list_of_files_or_glob 

È inoltre possibile utilizzare un parametro "-p" per rename.pl per averlo detto cosa avrebbe fatto, senza in realtà farlo.

Ho appena provato quanto segue nel mio c:/bin (ambiente cygwin/windows). Ho usato il "-p" in modo da sputare ciò che avrebbe fatto. Questo esempio divide solo la base e l'estensione e aggiunge una stringa tra loro.

perl c:/bin/rename.pl -p 's/^(.*?)\.(.*)$/\1-new_stuff_here.\2/' *.bat 

rename "here.bat" => "here-new_stuff_here.bat" 
rename "htmldecode.bat" => "htmldecode-new_stuff_here.bat" 
rename "htmlencode.bat" => "htmlencode-new_stuff_here.bat" 
rename "sdiff.bat" => "sdiff-new_stuff_here.bat" 
rename "widvars.bat" => "widvars-new_stuff_here.bat" 
+0

Grazie per la tua risposta! L'esempio di 'mv' era solo un esempio, però. Affronto la necessità di fare qualcosa di simile in molte altre situazioni, quindi suppongo che 'rename.pl' non funzioni per quei casi. – betabandido

+0

Beh, sì e no; puoi facilmente modificare rename.pl in * NOT * in realtà rinomina un file, ma fai qualcos'altro con esso. Come puoi vedere, con il parametro "-p", non rinomina affatto, ma stampa solo il risultato della trasformazione. Puoi usare quello script come base per fare molte altre cose, e l'argomento che ci vuole è un codice perl arbitrario, che è immensamente potente. Ma sicuramente siete i benvenuti. –

9

Se avete GNU Parallel http://www.gnu.org/software/parallel/ installato si può fare questo:

find . -name "*.txt" | parallel 'ext="{/}" ; mv -- {} foo/{/.}.bar.${ext##*.}' 

È possibile installare GNU parallelo semplicemente:

wget http://git.savannah.gnu.org/cgit/parallel.git/plain/src/parallel 
chmod 755 parallel 
cp parallel sem 

Guarda i video introduttivi per GNU parallelo per saperne di più: https://www.youtube.com/playlist?list=PL284C9FF2488BC6D1

+2

Raccomando anche GNU Parallel su 'xargs', è molto più potente e flessibile. Per quanto riguarda la sua installazione, suggerisco caldamente di usare il gestore di pacchetti della vostra distribuzione (come "sudo apt-get install parallel') o la procedura di installazione raccomandata di parallelo (come per http://git.savannah.gnu.org/cgit/parallel). git/tree/README): '(wget -O - pi.dk/3 || curl pi.dk/3/ || fetch -o - http://pi.dk/3) | bash' – MestreLion

+1

@MestreLion Si consiglia di non raccomandare l'anti-pattern curl-pipe-to-shell. Cos'è 'pi.dk'? Perché dovrei fidarmi di quel dominio? 'gnu.org' è almeno più affidabile. Ma nessuno degli URL utilizza TLS e, cosa più importante, nessuno dei due verifica una firma crittografica di ciò che viene scaricato. L'unica soluzione da raccomandare è 'apt-get'. – blujay

+0

Come si può vedere pi.dk/3 esegue una verifica della firma crittografica e si interrompe se la firma non corrisponde. Se ti senti a disagio a collegarlo direttamente a una shell, puoi salvarlo in un file, ispezionare il codice e quindi eseguirlo. –

18

Il seguente comando costruisce il comando move con xargs, sostituisce la seconda occorrenza di '.' con '.bar.', quindi esegue i comandi con bash, lavorando su mac OSX.

ls *.txt | xargs -I {} echo mv {} foo/{} | sed 's/\./.bar./2' | bash 
+1

Dirtay! Mi piace – g33kz0r

+0

Questo è un bel trucco – Zerodf

7

È possibile farlo in un passaggio (testato in GNU) evitando l'uso di assegnazioni di variabili temporanee

find . -name "*.txt" | xargs -I{} sh -c 'mv "$1" "foo/$(basename ${1%.*}).new.${1##*.}"' -- {} 
+1

Mi piace il modo in cui si utilizza la stringa di sostituzione come parametro posizionale per la shell in modo da poter eseguire sostituzioni ed espansioni. Molto bello – GL2014

0

Inspired mediante una risposta da @justaname sopra, questo comando che incorpora Perl one-liner lo farà:

find ./ -name \*.txt | perl -p -e 's/^(.*\/(.*)\.txt)$/mv $1 .\/foo\/$2.bar.txt/' | bash

Problemi correlati