joe di castrohttp://joedicastro.com2011-04-26T23:17:00+02:00Mover todos los archivos del mismo tipo de un arbol de directorios a la vez2011-04-26T23:17:00+02:00joe di castrohttp://joedicastro.com/mover-todos-los-archivos-del-mismo-tipo-de-un-arbol-de-directorios-a-la-vez.html<p>A veces tenemos la necesidad de <strong>mover (o copiar o borrar)</strong> varios ficheros
del mismo tipo a la vez, pero estos no "cuelgan" de un solo directorio, si no
que se encuentran repartidos dentro una jerarquía de subdirectorios. En este
caso los típicos comandos para mover/copiar/borrar archivos no nos sirven, ni
siquiera los útiles comodines nos resuelven el problema. ¿Como lo hacemos
entonces?</p>
<p>Por ejemplo, si tenemos una estructura de directorios como esta:</p>
<div class="codehilite"><pre>raiz
├── dir_1
│ ├── imagen_1.jpg
│ ├── pdf_1.pdf
│ ├── texto_1.txt
│ └── texto_2.txt
├── dir_2
│ ├── imagen_2.jpg
│ ├── imagen_3.gif
│ └── texto_3.txt
├── dir_3
│ ├── doc_1.doc
│ ├── pdf_2.pdf
│ ├── pdf_3.pdf
│ ├── pdf_4.pdf
│ ├── texto_4.txt
│ └── texto_5.txt
├── dir_4
│ ├── subdir_1
│ │ └── imagen_4.png
│ ├── subdir_2
│ │ ├── pdf_5.pdf
│ │ └── texto_7.txt
│ ├── doc_2.doc
│ └── texto_6.txt
└── dir_5
</pre></div>
<p>Y queremos mover todos los ficheros <code>.txt</code> al <code>dir_5</code> que tenemos vacío, ¿Como
lo hacemos? Bueno, en el caso de <strong>Linux</strong> (y UNIX) podemos hacerlo en una sola
operación gracias al siempre útil y versátil <code>find</code> con el siguiente comando
(ejecutado desde el directorio raíz):</p>
<div class="codehilite"><pre>find . -type f -name *.txt -exec mv <span class="o">{}</span> ./dir_5 <span class="se">\;</span>
</pre></div>
<p>Este comando nos encontraría todos los archivos con la extensión <code>.txt</code> dentro
de todos los directorios y subdirectorios y los iría moviendo uno a uno al
<code>dir_5</code>. Es un comando rápido y efectivo. Genéricamente, el comando sería así:</p>
<div class="codehilite"><pre>find directorio_origen -type f -name *.EXT -exec mv <span class="o">{}</span> ./directorio_destino <span class="se">\;</span>
</pre></div>
<p>donde <code>EXT</code> sería la extensión del tipo de archivo que queremos mover. Se puede
emplear esta variante para copiar los archivos en vez de moverlos:</p>
<div class="codehilite"><pre>find directorio_origen -type f -name *.EXT -exec cp <span class="o">{}</span> ./directorio_destino <span class="se">\;</span>
</pre></div>
<p>Y esta otra para eliminarlos (¡emplear con cuidado!):</p>
<div class="codehilite"><pre>find directorio_origen -type f -name *.EXT -exec rm -f <span class="o">{}</span> <span class="se">\;</span>
</pre></div>
<p>Partiendo de la idea de hacer algo equivalente en <strong>Python</strong>, he creado un
script que hace algo similar a esto, sin la necesidad de teclear todo el comando
(y con el riesgo a equivocarse y liarla parda) y que también sirve para que los
no trabajen habitualmente con la consola no necesiten recordar (o anotar) esos
comandos.</p>
<p>La parte principal del script <strong>move_by_ext.py</strong> es la siguiente:</p>
<div class="codehilite"><pre><span class="k">def</span> <span class="nf">find_and_process</span><span class="p">(</span><span class="n">args</span><span class="p">,</span> <span class="n">count</span><span class="o">=</span><span class="mi">0</span><span class="p">,</span> <span class="n">log</span><span class="o">=</span><span class="s">""</span><span class="p">):</span>
<span class="sd">"""Find the files by file extension and process (move/copy/remove) them."""</span>
<span class="k">def</span> <span class="nf">process</span><span class="p">(</span><span class="n">the_path</span><span class="p">,</span> <span class="n">the_file</span><span class="p">):</span>
<span class="sd">"""Process each file."""</span>
<span class="n">processed</span> <span class="o">=</span> <span class="mi">0</span>
<span class="n">src_file</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">the_path</span><span class="p">,</span> <span class="n">the_file</span><span class="p">)</span>
<span class="n">dst_file</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">args</span><span class="o">.</span><span class="n">dst</span><span class="p">,</span> <span class="n">the_file</span><span class="p">)</span>
<span class="k">if</span> <span class="n">args</span><span class="o">.</span><span class="n">rm</span><span class="p">:</span>
<span class="n">os</span><span class="o">.</span><span class="n">remove</span><span class="p">(</span><span class="n">src_file</span><span class="p">)</span>
<span class="n">processed</span> <span class="o">=</span> <span class="mi">1</span>
<span class="k">else</span><span class="p">:</span>
<span class="k">if</span> <span class="ow">not</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">exists</span><span class="p">(</span><span class="n">dst_file</span><span class="p">):</span> <span class="c"># not replace if already exists </span>
<span class="k">if</span> <span class="n">args</span><span class="o">.</span><span class="n">cp</span><span class="p">:</span>
<span class="n">shutil</span><span class="o">.</span><span class="n">copy2</span><span class="p">(</span><span class="n">src_file</span><span class="p">,</span> <span class="n">dst_file</span><span class="p">)</span>
<span class="k">else</span><span class="p">:</span>
<span class="n">shutil</span><span class="o">.</span><span class="n">move</span><span class="p">(</span><span class="n">src_file</span><span class="p">,</span> <span class="n">dst_file</span><span class="p">)</span>
<span class="n">processed</span> <span class="o">=</span> <span class="mi">1</span>
<span class="k">return</span> <span class="n">processed</span>
<span class="k">if</span> <span class="ow">not</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">exists</span><span class="p">(</span><span class="n">args</span><span class="o">.</span><span class="n">dst</span><span class="p">):</span>
<span class="n">os</span><span class="o">.</span><span class="n">mkdir</span><span class="p">(</span><span class="n">args</span><span class="o">.</span><span class="n">dst</span><span class="p">)</span>
<span class="k">for</span> <span class="n">path</span><span class="p">,</span> <span class="n">directories</span><span class="p">,</span> <span class="n">files</span> <span class="ow">in</span> <span class="n">os</span><span class="o">.</span><span class="n">walk</span><span class="p">(</span><span class="n">args</span><span class="o">.</span><span class="n">src</span><span class="p">):</span>
<span class="k">for</span> <span class="n">fil</span> <span class="ow">in</span> <span class="n">files</span><span class="p">:</span>
<span class="c"># ignore files without extension (can have the same name as the ext)</span>
<span class="n">file_ext</span> <span class="o">=</span> <span class="n">fil</span><span class="o">.</span><span class="n">split</span><span class="p">(</span><span class="s">'.'</span><span class="p">)[</span><span class="o">-</span><span class="mi">1</span><span class="p">]</span> <span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="n">fil</span><span class="o">.</span><span class="n">split</span><span class="p">(</span><span class="s">'.'</span><span class="p">))</span> <span class="o">></span> <span class="mi">1</span> <span class="k">else</span> <span class="bp">None</span>
<span class="c"># ignore dots in given extensions</span>
<span class="n">extensions</span> <span class="o">=</span> <span class="p">[</span><span class="n">ext</span><span class="o">.</span><span class="n">replace</span><span class="p">(</span><span class="s">'.'</span><span class="p">,</span> <span class="s">''</span><span class="p">)</span> <span class="k">for</span> <span class="n">ext</span> <span class="ow">in</span> <span class="n">args</span><span class="o">.</span><span class="n">ext</span><span class="p">]</span>
<span class="k">if</span> <span class="n">file_ext</span> <span class="ow">in</span> <span class="n">extensions</span><span class="p">:</span>
<span class="n">count</span> <span class="o">+=</span> <span class="n">process</span><span class="p">(</span><span class="n">path</span><span class="p">,</span> <span class="n">fil</span><span class="p">)</span>
<span class="n">opt</span> <span class="o">=</span> <span class="nb">int</span><span class="p">(</span><span class="s">"{0}{1}"</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="nb">int</span><span class="p">(</span><span class="n">args</span><span class="o">.</span><span class="n">rm</span><span class="p">),</span> <span class="nb">int</span><span class="p">(</span><span class="n">args</span><span class="o">.</span><span class="n">cp</span><span class="p">)),</span> <span class="mi">2</span><span class="p">)</span>
<span class="n">log</span> <span class="o">=</span> <span class="s">"Files {0}: {1}"</span><span class="o">.</span><span class="n">format</span><span class="p">({</span><span class="mi">0</span><span class="p">:</span><span class="s">"moved"</span><span class="p">,</span> <span class="mi">1</span><span class="p">:</span><span class="s">"copied"</span><span class="p">,</span> <span class="mi">2</span><span class="p">:</span><span class="s">"removed"</span><span class="p">}[</span><span class="n">opt</span><span class="p">],</span> <span class="n">count</span><span class="p">)</span>
<span class="k">return</span> <span class="n">log</span>
</pre></div>
<p>Este script nos permite realizar las mismas operaciones que los comandos
anteriores y además tiene dos ventajas adicionales:</p>
<ul>
<li>
<p><strong>Permite emplear varias extensiones a la vez</strong>, lo que nos permite
mover/copiar/borrar distintos tipos de archivo en una sola operación.
Por ejemplo si queremos mover todos los tipos de imagen de la estructura de
directorios anterior, podríamos hacerlo indicándole que emplee las extensiones
<code>.jpg</code>, <code>.gif</code> y <code>.png</code> en la misma operación. El comando sería el siguiente
(las extensiones pueden llevar o no el punto, funciona exactamente igual),
ejecutado desde el directorio raíz:</p>
<p><code>python move_by_ext.py jpg .gif png -d ./dir_5</code></p>
</li>
<li>
<p><strong>Funciona en Windows</strong>. A diferencia del comando para Linux, este se puede
emplear en Windows sin necesidad de instalar ningún entorno que nos simule un
shell Linux (como <a href="http://www.cygwin.com/">Cygwin</a>) y solo es necesario tener
instalado Python 2.7</p>
</li>
</ul>
<p>La sintaxis del script es muy sencilla y se puede ver reflejada en la ayuda del
mismo:</p>
<div class="codehilite"><pre><span class="gp">$</span> python move_by_ext.py -h
<span class="go">usage: move_by_ext.py ext [-s SRC] [-d DST] [-c | -r] [--help]</span>
<span class="go">Move (or copy/remove) all files selected by extension into a directory tree to</span>
<span class="go">a destination directory.</span>
<span class="go">positional arguments:</span>
<span class="go"> ext the extension(s) of the files to process. To use more</span>
<span class="go"> than one extension, separate them with a space</span>
<span class="go">optional arguments:</span>
<span class="go"> -h, --help show this help message and exit</span>
<span class="go"> -s SRC, --src SRC the source path. Current dir if none is provided</span>
<span class="go"> -d DST, --dst DST the destination path. Current dir if none is provided</span>
<span class="go"> -c, --copy copy all the files with the given extension(s) to the</span>
<span class="go"> destination directory.</span>
<span class="go"> -r, --remove remove all the files with the given extension(s). Use</span>
<span class="go"> with caution! remove also in the subdirectories</span>
<span class="go"> -v, --version show program's version number and exit</span>
</pre></div>
<p>El script completo puede encontrarse en el repositorio <em>Python Recipes</em> alojado
en <a href="http://github.com/joedicastro/python-recipes">github</a>.</p>