¿AppleScript puede obtener contenido de carpeta con exclusión de extensión de archivo?

Buscando una forma en AppleScript para obtener el contenido de un directorio con una exclusión para analizar. Sé cómo hacer esto usando findun bucle for con el comando:

find . -not -name "*.bat"

pero tengo curiosidad por saber si existe un enfoque exclusivo de AppleScript para obtener el contenido de una carpeta sin la extensión de archivo .bat.

Creé un directorio ficticio en mi escritorio, agregué varios archivos e intenté:

tell application "Finder"
    set theFiles to get every item of (entire contents of folder (choose folder)) whose kind ≠ ".bat"
    reveal theFiles
end tell

los .batarchivos están resaltados. Usando does not containcompilaciones pero errores a:

Error:

No se pueden obtener todos los elementos de la verdad.

Código:

tell application "Finder"
    set theFiles to get every item of (entire contents of folder (choose folder) does not contain ".bat")
    reveal theFiles
end tell

Intentando otro enfoque me sale un error de:

Error:

Finder recibió un error: Tipo de objeto desconocido.

Código:

tell application "Finder"
    set theFiles to get every item of (entire contents of folder (choose folder)) whose type ≠ {"public.bat"}
    reveal theFiles
end tell

Si trato de modificar el enfoque, solo puedo obtener un resultado sólido si reduzco el nombre de un archivo bat usando:

Código:

tell application "Finder"
    set theFiles to get (every file of folder (choose folder) whose name is not in {"foobar.bat"})
    reveal theFiles
end tell

En AppleScript puro, ¿hay alguna forma de obtener el contenido de un directorio con una exclusión por tipo de archivo? Posiblemente me gustaría encadenar la exclusión para múltiples tipos de archivos.

Respuestas (2)

Descubridor

Aquí hay algunas formas diferentes de filtrar por extensión de archivo, con una breve descripción de sus beneficios. A modo de ilustración, he realizado operaciones de Finder en una ventana de Finder abierta (mi carpeta Imágenes ), que tiene muchos archivos jpeg que sirven como una buena muestra para probar los filtros de exclusión.

tell application "Finder" to select (every file in the ¬
    front Finder window whose name does not end with ".jpg")

Esto tomó 14 segundos en su segunda ejecución, para proporcionar una línea de base para comparaciones aproximadas.

El simple acto de obligar al resultado a alias listacelerar constantemente el tiempo de ejecución a 8 segundos (casi el doble de rápido):

tell application "Finder" to select ((every file in the ¬
    front Finder window whose name does not end with ".jpg") as alias list)

Sorprendentemente para mí, filtrar por name extensionno pareció afectar los tiempos de rendimiento en comparación con el uso de la namepropiedad, lo que posiblemente sugiera que ambos están realizando comparaciones de cadenas equivalentes bajo el capó:

tell application "Finder" to select ((every file in the ¬
    front Finder window whose name extension is not "jpg") as alias list)

No obstante, preferiría filtrar por name extensionuna cuestión de elección personal, porque es una sintaxis más limpia.

Un beneficio de Finder es que podemos condensar múltiples filtros en una expresión inclusiva que esencialmente forma una lista de predicados:

tell application "Finder" to select ((every file in the ¬
    front Finder window whose name extension is not in ¬
    ["jpg", "png"]) as alias list)

y esto fue tan rápido para dos extensiones como para una, y luego nuevamente para cuatro:

tell application "Finder" to select ((every file in the ¬
    front Finder window whose name extension is not in ¬
    ["jpg", "png", "mov", "mp4"]) as alias list)

El filtrado por la namepropiedad no se puede realizar con múltiples extensiones de esta manera, por lo que se debe realizar con cláusulas separadas para cada extensión que desee excluir:

tell application "Finder" to select ((every file in the front Finder window whose ¬
    name does not end with "jpg" and ¬
    name does not end with "png" and ¬
    name does not end with "mp4" and ¬
    name does not end with "mov") as alias list)

Eventos del sistema

System Events está mucho más equipado para manejar el procesamiento y filtrado de archivos que Finder , lo cual es algo contradictorio. También evita que Finder se bloquee durante la operación.

No puede usar una lista de predicados como puede hacerlo con Finder , por lo que tenemos que conformarnos con las expresiones más largas:

tell application "System Events" to get path of every file in the ¬
    pictures folder whose visible = true and ¬
    name extension is not "jpg" and ¬
    name extension is not "png" and ¬
    name extension is not "mp4" and ¬
    name extension is not "mov"

tell application "Finder" to select the result

Pero el hecho de que esta operación dure aproximadamente cero segundos hace que valga la pena. Tenga en cuenta aquí que le pedí a System Events que devolviera pathestos elementos, simplemente para poder pasar el resultado a Finder y hacer que los seleccione como antes (así es como puede usarlo revealdespués de una llamada de System Events ).


C objetivo

Si necesita buscar carpetas enormes o una lista anidada de carpetas, no caiga en la tentación de usar la propiedad de Finder entire contents , o lo hará llorar. Utilice AppleScriptObjC, que realiza búsquedas profundas de árboles de carpetas con tiempos de rendimiento más rápidos que los eventos del sistema y el shell. Tiene un costo ligeramente mayor en comparación con System Events / Finder , pero probablemente comparable al de llamar a un script de shell.

Primero, es mejor definir algunos controladores para encargarse de la búsqueda y el filtrado de archivos:

use framework "Foundation"
use scripting additions

property this : a reference to current application
property NSDirectoryEnumerationSkipsHiddenFiles : a reference to 4
property NSDirectoryEnumerationSkipsPackageDescendants : a reference to 2
property NSFileManager : a reference to NSFileManager of this
property NSMutableSet : a reference to NSMutableSet of this
property NSPredicate : a reference to NSPredicate of this
property NSSet : a reference to NSSet of this
property NSString : a reference to NSString of this
property NSURL : a reference to NSURL of this


on filesInDirectory:fp excludingExtensions:exts
    local fp, exts
    
    set all to contentsOfDirectory at fp
    set |*fs| to all's filteredArrayUsingPredicate:(NSPredicate's ¬
        predicateWithFormat:("pathExtension IN %@") ¬
            argumentArray:[exts])
    
    set fs to NSMutableSet's setWithArray:all
    fs's minusSet:(NSSet's setWithArray:|*fs|)
    fs's allObjects() as list
end filesInDirectory:excludingExtensions:

on contentsOfDirectory at fp
    local fp
    
    set FileManager to NSFileManager's defaultManager()
    
    set fs to FileManager's enumeratorAtURL:(NSURL's ¬
        fileURLWithPath:((NSString's stringWithString:fp)'s ¬
            stringByStandardizingPath())) ¬
        includingPropertiesForKeys:[] ¬
        options:(NSDirectoryEnumerationSkipsHiddenFiles + ¬
        NSDirectoryEnumerationSkipsPackageDescendants) ¬
        errorHandler:(missing value)
    
    fs's allObjects()
end contentsOfDirectory

Luego, puede realizar una llamada como esta:

get my filesInDirectory:"~/Pictures" excludingExtensions:["jpg", "JPG", ""]
-- tell application "Finder" to reveal the result

Tenga en cuenta que Objective-C realizará búsquedas que distinguen entre mayúsculas y minúsculas, por lo que es prudente incluir extensiones de archivo para excluirlas en formato de mayúsculas y minúsculas. La cadena vacía excluye carpetas (excepto cuando el nombre de la carpeta contiene un punto).

Objective-C realizó una enumeración profunda de mi carpeta Imágenes filtrando todos los jpegs para devolver una lista de 643 archivos anidados dentro de ese directorio, y lo hizo en aproximadamente cero segundos . Ni Finder ni System Events pueden coincidir con esta hora ( Finder no responderá después de un tiempo, y hacer una búsqueda profunda con System Events requiere iterar manualmente a través de carpetas secundarias y decidir cómo manejar la lista anidada que devuelve, pero lo hace en unos impresionantes 4 segundos). El shell es igual de rápido para esta cantidad de archivos, pero estoy bastante seguro de lo que he leído de que Objective-C ejecutará el findcomando del shell para grandes volúmenes de archivos.

Verá que comenté el comando del Finder : pedirle al Finder que revele 643 archivos en diferentes carpetas fue... um... desagradable. no lo recomiendoreveal


Conclusión

① Utilice eventos del sistema para realizar búsquedas de archivos y, opcionalmente, recuperar rutas de archivos, que se pueden utilizar para cualquier operación que desee que Finder realice posteriormente sobre ellos.

② Si, por alguna razón, necesita usar Finder para realizar la búsqueda de archivos, siempre presione para alias listobtener beneficios de rendimiento y una clase de elemento que sea universalmente mucho más fácil de manejar más adelante.

③ Para estructuras de carpetas grandes o anidadas, use Objective-C.

Prueba esto :

tell application "Finder"
    set theFiles to files in folder (choose folder) whose name does not end with ".bat"
    reveal theFiles
end tell
Agregaré una advertencia de que, si bien esto debería funcionar, y es lo que solicitó OP, en mi experiencia, ejecutar el script de shell será mucho más rápido en un directorio con muchos archivos. No tengo idea de por qué.
Use Eventos del sistema en lugar de Finder para realizar la búsqueda de archivos, luego Finder para hacer la revelación. O, si solo desea usar Finder , pídale que devuelva una lista de alias , que también es más rápido.