Risolvere "mv: elenco degli argomenti troppo lungo"?


64

Ho una cartella con più di un milione di file che ha bisogno di essere ordinata, ma non posso davvero fare nulla perché mvgenera sempre questo messaggio

-bash: /bin/mv: Argument list too long

Sto usando questo comando per spostare i file senza estensione:

mv -- !(*.jpg|*.png|*.bmp) targetdir/

Risposte:


82

xargsè lo strumento per il lavoro. Quello o findcon -exec … {} +. Questi strumenti eseguono un comando più volte, con tutti gli argomenti che possono essere passati in una volta sola.

Entrambi i metodi sono più facili da eseguire quando l'elenco degli argomenti variabili è alla fine, il che non è il caso qui: l'argomento finale mvè la destinazione. Con le utility GNU (ovvero su Linux non incorporato o Cygwin), l' -topzione mvè utile per passare prima la destinazione.

Se i nomi dei file non hanno spazi bianchi né alcuno di essi \"', puoi semplicemente fornire i nomi dei file come input per xargs(il echocomando è incorporato in bash, quindi non è soggetto al limite di lunghezza della riga di comando):

echo !(*.jpg|*.png|*.bmp) | xargs mv -t targetdir

È possibile utilizzare l' -0opzione per xargsutilizzare l'input delimitato da null anziché il formato quotato predefinito.

printf '%s\0' !(*.jpg|*.png|*.bmp) | xargs -0 mv -t targetdir

In alternativa, è possibile generare l'elenco di nomi di file con find. Per evitare di ricorrere in sottodirectory, utilizzare -type d -prune. Poiché non viene specificata alcuna azione per i file di immagine elencati, vengono spostati solo gli altri file.

find . -name . -o -type d -prune -o \
       -name '*.jpg' -o -name '*.png' -o -name '*.bmp' -o \
       -exec mv -t targetdir/ {} +

(Ciò include i file di punti, a differenza dei metodi jolly della shell.)

Se non si dispone di utility GNU, è possibile utilizzare una shell intermedia per ottenere gli argomenti nell'ordine giusto. Questo metodo funziona su tutti i sistemi POSIX.

find . -name . -o -type d -prune -o \
       -name '*.jpg' -o -name '*.png' -o -name '*.bmp' -o \
       -exec sh -c 'mv "$@" "$0"' targetdir/ {} +

In zsh, puoi caricare il mvbuiltin :

setopt extended_glob
zmodload zsh/files
mv -- ^*.(jpg|png|bmp) targetdir/

o se si preferisce lasciare mve altri nomi continuano a fare riferimento ai comandi esterni:

setopt extended_glob
zmodload -Fm zsh/files b:zf_\*
zf_mv -- ^*.(jpg|png|bmp) targetdir/

o con globs in stile ksh:

setopt ksh_glob
zmodload -Fm zsh/files b:zf_\*
zf_mv -- !(*.jpg|*.png|*.bmp) targetdir/

In alternativa, usando GNU mve zargs:

autoload -U zargs
setopt extended_glob
zargs -- ./^*.(jpg|png|bmp) -- mv -t targetdir/

1
I primi due comandi hanno restituito "-bash:!: Evento non trovato" e i due successivi non hanno spostato alcun file. Sono su CentOS 6.5 se dovessi sapere
Dominique

1
@Dominique Ho usato la stessa sintassi globbing che hai usato nella tua domanda. Dovrai shopt -s extglobabilitarlo. Avevo perso un passo nei findcomandi, li ho riparati.
Gilles 'SO- smetti di essere malvagio'

Sto ottenendo questo con il comando find "find: invalid expression; hai usato un operatore binario '-o' con niente prima di esso." Ora proverò gli altri.
Dominique,

@Dominique I findcomandi che ho pubblicato (ora) funzionano. È necessario aver lasciato una parte durante il copia-incolla.
Gilles 'SO- smetti di essere malvagio'

Gilles, per i comandi Trova, perché non utilizzare l'operatore "no", !? È più esplicito e più facile da capire rispetto allo strano finale -o. Ad esempio,! -name '*.jpg' -a ! -name '*.png' -a ! -name '*.bmp'
CivFan,

13

Se lavorare con il kernel Linux è sufficiente, puoi semplicemente farlo

ulimit -s 100000

funzionerà perché il kernel Linux ha incluso una patch circa 10 anni fa che ha modificato il limite dell'argomento in base alla dimensione dello stack: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/ commit /? id = b6a2fea39318e43fee84fa7b0b90d68bed92d2ba

Aggiornamento: se ti senti coraggioso, puoi dire

ulimit -s unlimited

e starai bene con qualsiasi espansione della shell purché tu abbia abbastanza RAM.


Questo è un trucco. Come sapresti a cosa impostare il limite di stack? Ciò influisce anche su altri processi avviati nella stessa sessione.
Kusalananda

1
Sì, è un trucco. Il più delle volte questo tipo di hack è una tantum (con quale frequenza sposta comunque manualmente una quantità enorme di file?). Se sei sicuro che il processo non consumerà tutta la tua RAM, puoi impostare ulimit -s unlimitede funzionerà per file praticamente illimitati.
Mikko Rantalainen,

Con ulimit -s unlimitedil limite effettivo della riga di comando è 2 ^ 31 o 2 GB. ( MAX_ARG_STRLENnella fonte del kernel.)
Mikko Rantalainen,

9

Il limite di passaggio degli argomenti del sistema operativo non si applica alle espansioni che si verificano all'interno dell'interprete della shell. Quindi oltre a usare xargso find, possiamo semplicemente usare un loop di shell per suddividere l'elaborazione in singoli mvcomandi:

for x in *; do case "$x" in *.jpg|*.png|*.bmp) ;; *) mv -- "$x" target ;; esac ; done

Questo utilizza solo le funzioni e le utilità di POSIX Shell Command Language. Questa riga singola è più chiara con rientro, con punti e virgola non necessari rimossi:

for x in *; do
  case "$x" in
    *.jpg|*.png|*.bmp) 
       ;; # nothing
    *) # catch-all case
       mv -- "$x" target
       ;;
  esac
done

Con oltre un milione di file, questo genererà a sua volta più di un milione di mvprocessi, anziché solo i pochi necessari utilizzando la findsoluzione POSIX pubblicata da @Gilles. In altre parole, in questo modo si ottiene un sacco di churn CPU inutili.
CivFan,

@CivFan Un altro problema è convincerti che la versione modificata è equivalente all'originale. È facile vedere che l' caseaffermazione sul risultato *dell'espansione per filtrare diverse estensioni equivale !(*.jpg|*.png|*.bmp)all'espressione originale . La findrisposta non è infatti equivalente; discende in sottodirectory (non vedo un -maxdepthpredicato).
Kaz,

-name . -o -type d -prune -oprotegge dalla discesa in sottodirectory. -maxdepthapparentemente non è conforme a POSIX, sebbene ciò non sia menzionato nella mia findpagina man.
CivFan,

Rollback alla revisione 1. La domanda non dice nulla sulle variabili di origine o di destinazione, quindi questo aggiunge un'innegazione non necessaria alla risposta.
Kaz,

5

Per una soluzione più aggressiva di quelle precedentemente offerte, recupera il sorgente del kernel e modifica include/linux/binfmts.h

Aumenta la dimensione di MAX_ARG_PAGESqualcosa di più grande di 32. Ciò aumenta la quantità di memoria che il kernel consentirà per gli argomenti del programma, permettendoti così di specificare il tuo mvo il rmcomando per un milione di file o qualunque cosa tu stia facendo. Ricompila, installa, riavvia.

ATTENZIONE! Se lo imposti in modo troppo grande per la memoria di sistema e quindi esegui un comando con molti argomenti, SUCCESSI COSE SUCCESSIVE! Sii estremamente cauto nel fare ciò a sistemi multiutente, rende più facile per gli utenti malintenzionati utilizzare tutta la tua memoria!

Se non sai come ricompilare e reinstallare il kernel manualmente, probabilmente è meglio fingere che questa risposta non esista per ora.


5

Una soluzione più semplice utilizzando "$origin"/!(*.jpg|*.png|*.bmp)invece di un blocco catch:

for file in "$origin"/!(*.jpg|*.png|*.bmp); do mv -- "$file" "$destination" ; done

Grazie a @Score_Under

Per uno script multilinea è possibile effettuare le seguenti operazioni (si noti che ;prima che donevenga eliminato):

for file in "$origin"/!(*.jpg|*.png|*.bmp); do        # don't copy types *.jpg|*.png|*.bmp
    mv -- "$file" "$destination" 
done 

Per fare una soluzione più generalizzata che sposta tutti i file, è possibile eseguire il one-liner:

for file in "$origin"/*; do mv -- "$file" "$destination" ; done

Che assomiglia a questo se fai rientro:

for file in "$origin"/*; do
    mv -- "$file" "$destination"
done 

Questo prende tutti i file nell'origine e li sposta uno alla volta nella destinazione. Le virgolette $filesono necessarie nel caso in cui ci siano spazi o altri caratteri speciali nei nomi dei file.

Ecco un esempio di questo metodo che ha funzionato perfettamente

for file in "/Users/william/Pictures/export_folder_111210/"*.jpg; do
    mv -- "$file" "/Users/william/Desktop/southland/landingphotos/";
done

È possibile utilizzare qualcosa come il glob originale nel for-loop per ottenere una soluzione più vicina a ciò che viene richiesto.
Score_Under

Cosa intendi con glob originale?
Whitecat,

Scusate se questo era un po 'criptico, mi riferivo al glob nella domanda: !(*.jpg|*.png|*.bmp). Potresti aggiungerlo al tuo for-loop facendo globbing, il "$origin"/!(*.jpg|*.png|*.bmp)che eviterebbe la necessità dell'interruttore usato nella risposta di Kaz e manterrebbe il corpo semplice del for-loop.
Score_Under

Punteggio fantastico. Ho incorporato il tuo commento e aggiornato la mia risposta.
Whitecat,

3

A volte è più semplice scrivere una piccola sceneggiatura, ad esempio in Python:

import glob, shutil

for i in glob.glob('*.jpg'):
  shutil.move(i, 'new_dir/' + i)

1

Puoi aggirare quella limitazione mentre usi ancora mvse non ti dispiace eseguirla un paio di volte.

È possibile spostare porzioni alla volta. Diciamo ad esempio che avevi un lungo elenco di nomi di file alfanumerici.

mv ./subdir/a* ./

Che funzioni. Quindi metti fuori un altro grosso pezzo. Dopo un paio di mosse, puoi semplicemente tornare a utilizzaremv ./subdir/* ./


0

Ecco i miei due centesimi, aggiungilo a .bash_profile

mv() {
  if [[ -d $1 ]]; then #directory mv
    /bin/mv $1 $2
  elif [[ -f $1 ]]; then #file mv
    /bin/mv $1 $2
  else
    for f in $1
    do
      source_path=$f
      #echo $source_path
      source_file=${source_path##*/}
      #echo $source_file
      destination_path=${2%/} #get rid of trailing forward slash

      echo "Moving $f to $destination_path/$source_file"

      /bin/mv $f $destination_path/$source_file
    done
  fi
}
export -f mv

uso

mv '*.jpg' ./destination/
mv '/path/*' ./destination/
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.