Utilizzo di SAS Macro per reindirizzare un elenco di nomi di file da una directory di Windows
Domanda
Sto cercando di modificare la macro di seguito per accettare un parametro macro come argomento 'location' per un comando dir. Tuttavia, non riesco a risolverlo correttamente a causa del problema delle citazioni nidificate. L'uso di% str (% ') non funziona, né di quotare le funzioni per qualche motivo.
La macro funzionerà bene quando il percorso del file non ha nessuno spazio (ad es. C: \ temp \ withnospace) poiché non sono necessarie le virgolette centrali. Tuttavia, ho bisogno che questa macro funzioni per i percorsi di file con spazi (ad esempio 'C: \ temp \ with space \').
Per favore aiutatemi!
%macro get_filenames(location)
filename pipedir pipe "dir &location. /b " lrecl=32767;
data filenames;
infile pipedir truncover;
input line $char1000.;
run;
%mend;
%get_filenames(C:\temp\) /* works */
%get_filenames('C:\temp\with space') /* doesnt work */
Soluzione
Ecco un altro modo per ottenere lo stesso risultato senza la necessità di utilizzare un TUBO.
%macro get_filenames(location);
filename _dir_ "%bquote(&location.)";
data filenames(keep=memname);
handle=dopen( '_dir_' );
if handle > 0 then do;
count=dnum(handle);
do i=1 to count;
memname=dread(handle,i);
output filenames;
end;
end;
rc=dclose(handle);
run;
filename _dir_ clear;
%mend;
%get_filenames(C:\temp\);
%get_filenames(C:\temp\with space);
%get_filenames(%bquote(C:\temp\with'singlequote));
Altri suggerimenti
Apporta le seguenti modifiche e il tuo codice funzionerà.
%macro get_filenames(location); %*--(1)--*;
filename pipedir pipe "dir ""%unquote(&location)"" /b" lrecl=32767; %*--(2)--*;
data filenames;
infile pipedir truncover;
input filename $char1000.;
put filename=;
run;
filename pipedir clear; %*--(3)--*;
%mend;
%get_filenames(d:\)
%get_filenames(d:\your dir) %*--(4)--*;
(1) Termina l'istruzione % macro
con un punto e virgola;
(2) Circonda la risoluzione della variabile macro con doppie virgolette raddoppiate e % unquote
;
(3) Rilasciare l'handle del file cancellandolo; e
(4) Non citare una sola volta il parametro di input. citazione macro invece, se necessario.
Basato sull'ultimo esempio di questo pagina , anziché l'istruzione del nome file, prova
%let filrf=pipedir;
%let rc=%sysfunc(filename(filrf,%bquote(dir "&location" /b),pipe));
e chiama la macro senza usare le virgolette:
%get_filenames(c:\temp\with spaces);
Ho anche provato a citare macro, ma non sono riuscito a farlo funzionare.
ecco una breve macro per inserire gli elenchi di directory basati su Windows in un set di dati sas.
%macro DirList(dir); /* %if &SUBDIR eq %then %let subdir=/s; */ /*** &SUBDIR not defined ****/ filename dirpipe pipe "dir &DIR.\*.* /s /-c"; data dir_list(label="Directory Listing [&DIR.]" drop=re_: _line_ date time); format Path File $250. ModDT datetime19. Size 16. _line_ $32000. ; if _N_ = 1 then do; re_path=prxparse("/Directory of (.+)/"); re_subd=prxparse("/(\d\d\/\d\d\/\d\d\d\d)\s+(\d\d:\d\d [A|P]M)\s+\s+(\S.*)/"); re_file=prxparse("/(\d\d\/\d\d\/\d\d\d\d)\s+(\d\d:\d\d [A|P]M)\s+(\d+)\s+(\S.*)/"); retain re_: path; end; infile dirpipe lrecl=32000; input; _line_ = _infile_; if lengthn(_line_)=0 then delete; else if prxmatch(re_path, _line_) then do; path=prxposn(re_path, 1, _line_); end; else if prxmatch(re_subd, _line_) then do; date=input(prxposn(re_subd, 1, _line_), mmddyy10.); time=input(prxposn(re_subd, 2, _line_), time6.); ModDT=dhms(date, 0, 0, time); File=prxposn(re_subd, 3, _line_); size = .D; /*mark subdirectory records*/ if file not in ('.', '..') then output; end; else if prxmatch(re_file, _line_) then do; date=input(prxposn(re_file, 1, _line_), mmddyy10.); time=input(prxposn(re_file, 2, _line_), time6.); ModDT=dhms(date, 0, 0, time); size=input(prxposn(re_file, 3, _line_), 16.); file=prxposn(re_file, 4, _line_); output; end; run; filename dirpipe clear; %mend;
ed ecco come vengono chiamati
%dirlist(c:); %dirlist(c:\temp);
si noti che non è presente una barra rovesciata finale quando si specifica la directory di base. C:
non C: \
.
funziona per me se chiamo la macro originale in questo modo
%get_filenames(""C:\Program Files"")
ovviamente ho dovuto aggiungere il punto e virgola alla fine dell'istruzione % macro
.
se la tua directory contiene una virgola, accadono cose brutte. per risolvere, usa la % str ()
macro
%get_filenames(%str(C:\temp\comma, fail))
Ecco una versione pura di codice macro. Consente inoltre di specificare che si desidera conoscere solo i file (e non le cartelle) e consente di specificare un filtro di base. Restituisce l'elenco dei file in un formato delimitato ma è possibile inserirli facilmente in un set di dati utilizzando l'inserimento SQL se si desidera (esempio incluso ma non testato - nessun accesso ATM SAS). Può essere chiamato da qualsiasi luogo - all'interno di un'altra macro, un set di dati, un'istruzione sql ... ovunque. Basta aggiungere queste due macro alla libreria macro di autocall e hai ragione.
Ci sono 2 macro di seguito. La macro% isdir è richiesta dalla macro% file_list. Le macro sono un po 'più grandi e più complesse di quanto sopra ma sono MOLTO più flessibili. Inoltre forniscono il controllo degli errori.
/******************************************************************************
** PROGRAM: ISDIR.SAS
**
** DESCRIPTION: DETERMINES IF THE SPECIFIED PATH EXISTS OR NOT.
** RETURNS: 0 IF THE PATH DOES NOT EXIST OR COULD NOT BE OPENED.
** 1 IF THE PATH EXISTS AND CAN BE OPENED.
**
** PARAMETERS: iPath: THE FULL PATH TO EXAMINE. NOTE THAT / AND \ ARE TREATED
** THE SAME SO &SASDIR/COMMON/MACROS IS THE SAME AS
** &SASDIR\COMMON\MACROS.
**
******************************************************************************/
%macro isDir(iPath=,iQuiet=1);
%local result dname;
%let result = 0;
%if %sysfunc(filename(dname,&iPath)) eq 0 %then %do;
%if %sysfunc(dopen(&dname)) %then %do;
%let result = 1;
%end;
%else %if not &iQuiet %then %do;
%put ERROR: ISDIR: %sysfunc(sysmsg());
%end;
%end;
%else %if not &iQuiet %then %do;
%put ERROR: ISDIR: %sysfunc(sysmsg());
%end;
&result
%mend;
%put %isDir(iPath=&sasdir/common/macros);
%put %isDir(iPath=&sasdir/kxjfdkebnefe);
%put %isDir(iPath=&sasdir/kxjfdkebnefe, iQuiet=0);
%put %isDir(iPath=c:\temp);
/******************************************************************************
** PROGRAM: FILE_LIST.SAS
**
** DESCRIPTION: RETURNS THE LIST OF FILES IN A DIRECTORY SEPERATED BY THE
** SPECIFIED DELIMITER. RETURNS AN EMPTY STRING IF THE THE
** DIRECTORY CAN'T BE READ OR DOES NOT EXIST.
**
** PARAMETERS: iPath : THE FULL PATH TO EXAMINE. NOTE THAT / AND \ ARE
** TREATED THE SAME SO &SASDIR/COMMON/MACROS IS THE
** SAME AS &SASDIR\COMMON\MACROS. WORKS WITH BOTH UNIX
** AND WINDOWS.
** iFilter : SPECIFY A BASIC FILTER TO THE FILENAMES, NO REGULAR
** EXPRESSIONS OR WILDCARDS.
** iFiles_only: 0=RETURN FILES AND FOLDERS
** 1=RETURN FILES ONLY.
** iDelimiter : SPECIFY THE DELIMITER TO SEPERATE THE RESULTS BY.
******************************************************************************/
/*
** TODO: DOESNT CATER FOR MACRO CHARS IN FILENAMES. FIX SOMETIME.
** TODO: IMPROVE THE FILTER. JUST A SIMPLE IF STATEMENT AT THE MOMENT.
*/
%macro file_list(iPath=, iFilter=, iFiles_only=0, iDelimiter=|);
%local result did dname cnt num_members filename;
%let result=;
%if %sysfunc(filename(dname,&iPath)) eq 0 %then %do;
%let did = %sysfunc(dopen(&dname));
%let num_members = %sysfunc(dnum(&did));
%do cnt=1 %to &num_members;
%let filename = %sysfunc(dread(&did,&cnt));
%if "&filename" ne "" %then %do;
%if &iFiles_only %then %do;
%if not %isDir(iPath=&iPath/&filename) %then %do;
%if "&iFilter" ne "" %then %do;
%if %index(%lowcase(&filename),%lowcase(&iFilter)) %then %do;
%let result = &result%str(&iDelimiter)&filename;
%end;
%end;
%else %do;
%let result = &result%str(&iDelimiter)&filename;
%end;
%end;
%end;
%else %do;
%if "&iFilter" ne "" %then %do;
%if %index(%lowcase(&filename),%lowcase(&iFilter)) %then %do;
%let result = &result%str(&iDelimiter)&filename;
%end;
%end;
%else %do;
%let result = &result%str(&iDelimiter)&filename;
%end;
%end;
%end;
%else %do;
%put ERROR: (CMN_MAC.FILE_LIST) FILE CANNOT BE READ.;
%put %sysfunc(sysmsg());
%end;
%end;
%end;
%else %do;
%put ERROR: (CMN_MAC.FILE_LIST) PATH DOES NOT EXIST OR CANNOT BE OPENED.;
%put %sysfunc(sysmsg());
%end;
/*
** RETURN THE RESULT. TRIM THE LEADING DELIMITER OFF THE FRONT OF THE RESULTS.
*/
%if "&result" ne "" %then %do;
%substr(&result,2)
%end;
%mend;
**
** EXAMPLES - HAVENT TESTED THE LAST TWO YET BUT THEY SHOULD WORK IF SYNTAX IS CORRECT
*;
%put %file_list(iPath=c:\temp);
%put %file_list(iPath=c:\xxdffsds);
%put %file_list(iPath=c:\rob\SASDev\, iFilter=a);
%put %file_list(iPath=c:\rob\SASDev\,iFiles_only=1);
%put %file_list(iPath=/tmp/unix_sasdir,iFiles_only=1);
data x;
file_list = "%file_list(iPath=c:\temp)";
run;
proc sql noprint;
insert into my_table values ("%file_list(iPath=c:\temp,iDelimiter=%str(","))");
quit;
Eccone uno che decodifica l'ordine di quotazione e annullamento della quotazione:
%let command =%unquote(%str(%')dir "&baseDir.data\*txt"%str(%'));
filename datain pipe &command;
dove la variabile macro basedir può contenere spazi, così come i nomi dei file.
Questa combinazione di
% unquote
e % str (% ')
è un linguaggio macro che si verifica frequentemente.
" cosa succede se ho una virgoletta singola nella mia directory? "
La gestione di questa situazione richiede una funzione di quotazione di macro, come % bquote ();
Continuando l'esempio sopra, questo:
%let command =%unquote(%str(%')dir "%bquote(&baseDir.data\*txt)"%str(%'));
dovrebbe farlo.
Per evitare infinite iterazioni di questo tipo di domanda, guarda L'articolo di Ian Whitlock, Uno sguardo serio alle citazioni macro, disponibile qui ;
Ce ne sono (molti) altri, ma questo è il più ampiamente citato. Un piccolo appunto: probabilmente vale la pena qualsiasi cosa di Ian Whitlock. Scrive chiaramente e la sua comprensione dei problemi con SAS è fantastica.
Usiamo questa piccola macro
%macro getdir(dir=,redirect=, switch=);
options noxwait xsync;
%if %length(&switch)=0 %then %let switch=b;
data _null_;
xcmd='dir "' || "&dir" || '"' || "/&switch " || ">" || "&redirect";
put 'generated the following command: ' xcmd=;
rc=system(xcmd);
put 'result code of above command: ' rc=;
run;
%mend getdir;
Esempio di chiamata
%getdir(dir=c:\temp\,redirect=c:\temp\dir.txt) *run;
Se si esegue in batch e non si dispone dell'opzione noxwait xsync
, il lavoro si bloccherà sul server in attesa di una risposta dell'operatore.