ラベル Coding の投稿を表示しています。 すべての投稿を表示
ラベル Coding の投稿を表示しています。 すべての投稿を表示

2025年5月29日木曜日

ライブラリ参照名を定義しないでデータセットを格納する例

 ライブラリ参照名をテンポラリで作りたくないときがあります。そんなときにはディレクトリを指定してライブラリ参照名を定義しない使い方があります。古いTipsだけど忘れていた。



2025年5月13日火曜日

basename, dirname, nobs

良く使うけど、良く忘れるファイル名の操作のマクロをまとめておきます。DATAステップは使わずにマクロの文だけで作っているのでDATAステップの中でも値をはめこみ易いのです。高度なエディタだとマクロの終端を認識してくれないことがあるので、これらのマクロは最後の方に書きます。
このマクロの弱点は、括弧とか特殊文字がファイル名やディレクトリに混じると文字列の終端が判定できなくてERRORになることです。括弧を含む又は括弧の開始があって終了がないようなときはERRORになるので要注意です。

/* Strip directory and suffix from filenames. */
%macro basename(path);
  %local dequotePath orgLength dequoteLength lastSlash x;

  %let dequotePath=%sysfunc(dequote(&path));
  %let orgLength=%length(&path);
  %let dequoteLength=%length(&dequotePath);
  
  %let lastSlash=%sysfunc(findc("&dequotePath", "/", -%length(&dequotePath)));
  %let x=%substr(&dequotePath, %eval(&lastSlash));
  %quote(&x)
%mend;

/* Strip non-directory suffix from file name */
%macro dirname(path);
  %local dequotePath orgLength dequoteLength lastSlash x;

  %let dequotePath=%sysfunc(dequote(&path));
  %let orgLength=%length(&path);
  %let dequoteLength=%length(&dequotePath);
  
  %let lastSlash=%sysfunc(findc("&dequotePath", "/", -%length(&dequotePath)));
  %let x=%substr(&dequotePath, 1, %eval(&lastSlash-2));
  %quote(&x)
%mend;

/* Return number of obs. */
%macro nobs(mydata);
  %local NOBS;
  %let NOBS=0;
  %if %sysfunc(exist(&mydata)) %then %do;
    %let mydataID=%sysfunc(OPEN(&mydata., IN));
    %let NOBS=%sysfunc(ATTRN(&mydataID, NOBS));
    %let RC=%sysfunc(CLOSE(&mydataID));
  %end;
  &NOBS
%mend;

2024年2月14日水曜日

マクロ変数の名前と値をログに出力する。

 何度も忘れているのでメモします。これでマクロ変数名と値をログに出力できます。DATAステップのPUTと書き方が違うので、ぱっと思い出せない。

%put &=var;

マニュアルで %put のセクションには書かれていない。出典がどこにあるのか探す。
https://go.documentation.sas.com/doc/ja/sasstudiocdc/3.8/pgmsascdc/mcrolref/n189qvy83pmkt6n1bq2mmwtyb4oe.htm


2024年2月13日火曜日

PROC HTTP で HTTPSではなくHTTPを使って通信するときのオプション

 最近はHTTPではなくHTTPSが当たり前に使われているが、内部の通信だとHTTPが使われていることがある。そのときに必要な PROC HTTPのオプションは、sslparms "SSLREQCERT"="ALLOW"; です。

マニュアルからこのSSLREQCERT=ALLOWの意味を引くと、「ALLOWは、クライアントがサーバ証明書を要求することを指定するが、証明書が提供されなかったり、無効な証明書が提供されたりしても、セッションは正常に進行する」です。PROC HTTPのデフォルトは、SSLREQCERT=DEMANDです。意味は「サーバ証明書が要求され、有効な証明書が提供されなければセッションが終了することを指定する」です。システムオプションでも設定できます。

よく curl、sas-viya CLIで --insecure, -k のオプションありますが。これは SSLREQCERT=NEVER と同じです。証明書のチェックはしません。ALLOWの方が証明書あり、無し、どちらでも対応できるので、例としてはALLOWが使われているのが多いと思います(推測)。

2024年1月17日水曜日

PROC HTTPでトークンを取得するサンプル

PROC HTTPでユーザ名とパスワードを指定してアクセストークンを取得するサンプルです。通常はOAUTH_BEARER=SAS_SERVICES を使うことが多いですが、稀にトークン取得したいというニーズがあります。このコードで?な箇所は認証のヘッダーにsas.cli:をエンコードした値を使っているところです。コロンの前がクライアントのツール、コロンの後にパスワードを指定するのが普通ですが、パスワードは空の状態でエンコードしています。このパラメータはSAS Viya CLIからauth loginするときに--verboseで出力されるデバッグの情報を見てそれに倣っています。クライアントのパスワードが空なのは理解できませんが、ログを見るとそうなっています。時間あるときにPostmanでも同じようにパラメータを指定して動くか確かめます。

/* PROC Sample of using HTTP to obtain an access token. */
%let client=sas.cli;
%let cpassword=;
%let username=alex;
%let password=hogehoge;
%let host=%sysget(SAS_SERVICES_URL);
filename resp "/tmp/foo.txt";

data _null_;
	attrib buf length=$64;
	buf=put("&client.:&cpassword.", $base64x64.);
	put buf=;
	call symputx('code', compress(buf), 'G');
run;

proc http method="POST" url="&host./SASLogon/oauth/token" 
		in="grant_type=password%str(&)username=&username.%str(&)password=&password." out=resp timeout=15;
	headers "authorization"="Basic &code" 
		"Content-Type"="application/x-www-form-urlencoded";
run;

data _null_;
	infile resp truncover;
	input;
	put _INFILE_;
run;

2023年11月21日火曜日

SAS StudioからOpenSearchのログを検索するサンプル

Viyaのログの調査の効率を上げるためにSAS Studioからクエリを投入できないか思案しています。できたのが以下のコードです。

/* Sample code to search OpenSearch by PROC HTTP. */
filename myresult temp;

data _null_;
	attrib code length=$64;

	/* Change the following user name (admin) and password (hogehoge) to match your environment. */
	code=put("admin:hogehoge", $base64x64.);
	put code=;
	call symputx('code', compress(code), 'G');
run;

proc http url="https://v4m-search.logging.svc.cluster.local:9200/_search"
		in='{ "query": { "simple_query_string": { "query": "setinit", "fields": [ "message" ], "default_operator": "and" }}}' 
		out=myresult
		timeout=30
		/* clear_cache*/;
	sslparms "SSLREQCERT"="allow" SSLCERTLOC="/opt/sas/viya/home/SASSecurityCertificateFramework/tls/certs/ca-bundle.pem";
	headers "content-type"="application/json" "authorization"="Basic &code.";
	debug level=0;
run;

data _null_;
	infile myresult;
	input;
	put _INFILE_;
run;
このコードが意味するところは、messageフィールドからsetinitを検索しています。URLのホスト名、ポート番号とPEMのファイルはSAS Compute ServerのPodに接続して探したり、kubectl describe endpointsやservicesで探しました。それらのホスト名とかポート番号は公式なドキュメントには書かれていないので実機検証で調べてたものです。なので内部の実装は知らぬ間に変わる可能性があることに留意してください。

CURLの--insecureに相当するのが、SSLREQCERTのオプションです。それだけだとエラーが出るので、SSLCERTLOCでPEMファイルを指定しています。

私は全く知らなかったのですが、これでPROC HTTPを実行すると接続とCOOKEの情報が保持されるそうです。それをクリアするのがコメントアウトしてある CLEAR_CACHEです。その接続とCOOKIEがあれば、毎回SSLSERTLOCを指定する必要はありません。

2023年10月4日水曜日

OpenSearch を curl から検索する例

SAS Viya Monitoring for Kubernetes でログを検索したい場合に以下のようにクエリを投入できる。結果はJSONなので、これを加工して欲しい情報を見やすく成型する。この curl のコマンドは OpenSearch の DEV Toolからコマンドを生成して、必要最小限のオプションまで削りました。

curl 'https://viya4.example.com/dashboards/api/console/proxy?path=_search&method=GET' \
  -H 'osd-xsrf: opensearchDashboards' \
  --data-raw '{ "query": { "simple_query_string": { "query": "setinit", "fields": [ "message" ], "default_operator": "and" }}}' \
  --insecure -u 'admin:password'

このコードが意味するところは、messageフィールドからsetinitを検索しています。-uでユーザ名とパスワードを指定しています。OpenSearch自体にクエリを投げる例はすぐに見つかりますが、クラスタ内部のサービスを指定する方法が判らず数時間調べました。

2023年4月25日火曜日

Viya4 XCMD有効化の設定

 良く忘れるのでメモ
allowXCMD がキーワード



2023年4月11日火曜日

JSONのファイルから32KBを超えるSASプログラムを抽出する。

LIBNAMEのJSONエンジンの欠点は、32KBを超える長さのデータを抽出できない点です。できないものは仕方ないので、32KBを超えてもSASプログラムを抽出するコードを書きました。 字句解析の状態遷移を考えて "code": の値を抽出してバックスラッシュのエンコードを解いてファイルの保存します。入力のJSONファイルは整形されていないコンパクトや書式の前提です。昔、Lex, Yaccを使っていた記憶を掘り起こした。

/* Code extraction from JSON file of job definition */
%macro extractJobCode(infile=, outfile=, debug=0);
	%local/readonly DQ='22'x;
	%local/readonly BSL='5c'x;
	%local/readonly BS='08'x;
	%local/readonly FF='0c'x;
	%local/readonly NL='0a'x;
	%local/readonly CR='0d'x;
	%local/readonly TB='09'x;
	%local/readonly SP='20'x;

	data _NULL_;
		attrib filein length=8 label='input file id';
		attrib fileid length=8 label='output file id';
		attrib stat length=8 label='State of lexical analysis';
		attrib token length=$256 label='Token';
		attrib ucode length=$4 label='Unicode';
		attrib count length=8;

		/* Open input/output files */
		filein=fopen("&infile" , 'I', 1, 'B');
		fileid=fopen("&outfile", 'O', 1, 'B');
		count=0;
		stat=0;
		rec=&SP;

		do while(fread(filein)=0);
			rc=fget(filein, rec, 1);

			%if &debug=1 %then
				%do;

					if count < 512 then
						put stat=rec=token=;
					count=count + 1;
				%end;

			if stat eq 0 and rec eq &DQ then
				stat=1;
			else if stat eq 1 then
				do;

					if rec eq &DQ then
						do;

							/* Double quotes */
							if token eq 'code' then
								stat=2;
							else
								stat=0;
							token='';
						end;
					else
						do;
							token=catt(token, rec);
							stat=1;
						end;
				end;
			else if stat eq 2 then
				do;

					if rec in (&SP, &TB, &NL) then
						stat=2;
					else if rec eq ':' then
						stat=3;
					else
						stat=0;
				end;
			else if stat eq 3 then
				do;

					if rec in (&SP, &TB, &NL) then
						stat=3;
					else if rec eq &DQ then
						stat=4;
					else
						stat=0;
				end;
			else if stat eq 4 then
				do;

					if rec eq &DQ then
						stat=0;
					else if rec eq &BSL then
						stat=5;
					else
						do;
							rc=fput(fileid, rec);
							rc=fwrite(fileid);
						end;
				end;
			else if stat eq 5 then
				do;

					/* Handling of backslash-escaped characters */
					if rec eq 'b' then
						do;
							rc=fput(fileid, &BS);
							rc=fwrite(fileid);
							stat=4;
						end;
					else if rec eq &DQ then
						do;
							rc=fput(fileid, &DQ);
							rc=fwrite(fileid);
							stat=4;
						end;
					else if rec eq &BSL then
						do;
							rc=fput(fileid, &BSL);
							rc=fwrite(fileid);
							stat=4;
						end;
					else if rec eq 'f' then
						do;
							rc=fput(fileid, &FF);
							rc=fwrite(fileid);
							stat=4;
						end;
					else if rec eq 'n' then
						do;
							rc=fput(fileid, &NL);
							rc=fwrite(fileid);
							stat=4;
						end;
					else if rec eq 'r' then
						do;
							rc=fput(fileid, &CR);
							rc=fwrite(fileid);
							stat=4;
						end;
					else if rec eq 't' then
						do;
							rc=fput(fileid, &TB);
							rc=fwrite(fileid);
							stat=4;
						end;
					else if rec eq 'u' then
						do;
							ucode='';
							do i=1 to 4;
								rc=fread(filein);
								rc=fget(filein, rec, 1);
								ucode=catt(ucode, rec);
							end;
							if ucode eq '0026' then
								do;
									rc=fput(fileid, '&');
									rc=fwrite(fileid);
								end;
							else if ucode eq '003c' then
								do;
									rc=fput(fileid, '<');
									rc=fwrite(fileid);
								end;
							else if ucode eq '003e' then
								do;
									rc=fput(fileid, '>');
									rc=fwrite(fileid);
								end;
							else
								do;
									rc=fput(fileid, &BSL);
									rc=fput(fileid, 'u');
									rc=fput(fileid, ucode);
									rc=fwrite(fileid);
								end;
							stat=4;
						end;
				end;
		end;

		/* Close input/output files */
		rc=fclose(filein);
		rc=fclose(fileid);
	run;

%mend;

2022年12月19日月曜日

遅い PROC SQLのデバッグ

PROC SQLに 昔からあるオプション、機能だが使うのは初めて _METHOD _TREEを試しました。処理時間に問題ありということで、PROC SQL _METHOD _TREE; という具合にオプションを足すと、クエリを評価する順序や評価のツリー構造をログに出力してくれます。

これ DATAステップでインデクス作ったのに使っていないとかログを見ると判ります。性能調整のヒントに FULLSTIMERと一緒に合わせて使えます。

2022年11月1日火曜日

ユニークなライブラリ参照名、ファイル参照名を定義するマクロ

マクロを書いていて地味に困るのがテンポラリのファイル参照名、ライブラリ参照名を定義することです。ユニークでぶつからない名前を定義するために、小さいコードを書きました。参照するファイルが、SASファイルでも動くようにアンダースコアで始まるようにしています。filename関数でテンポラリの名前を作ると #LN00058 のような名前になります。これだと正しくアサインできないときがあるので、アンダースコア始まりにしています。

それと、マクロの定義を削除するステートメントを良く忘れるのでメモしておきます。%SYSMACDELETE で定義したマクロを削除します。AUTOEXECの中で使うけど最後には後始末しておきたいときに使います。

%macro _uname();
  %local n;
  %let n=%sysfunc(monotonic());
  _LN%sysfunc(putn(&n, z5.))
%mend;

%put %_uname;

2022年10月25日火曜日

CPORTで移送ファイルを作成と圧縮

CPORTで移送ファイルを作成するときに圧縮できることを実験で確認した。移送ファイル形式は圧縮されてはいるが圧縮率は高くない。なので、FILENAMEステートメントでZIPの圧縮を試してみた。圧縮した移送ファイルからCIMPORTできて /tmp/new のディレクトリにもSASファイルがあることを確認できた。。

filename myfile1 '/tmp/foo.xpt';

proc cport lib=sashelp file=myfile1 memtype=data;
   select air baseball class;
run;

filename myfile2 zip "/tmp/foo.xpt.zip";

proc cport lib=sashelp file=myfile2 memtype=data;
   select air baseball class;
run;

libname newlib '/tmp/new';

proc cimport lib=newlib infile=myfile2;
run;

2022年9月21日水曜日

SAS/ACCESSでIn-Database処理になるか?確認する方法

 SAS/ACCESSのデータベースを乗り換えたときに、WHERE句の関数がプッシュダウンされているか知りたいという問合せがあった。DBの種類にもよるかもしれないが、OBS=1 を指定すると SQLにLMIT=1が効く場合がある。これでSASログを確認してプッシュダウンされない部分を特定していく。

options sastrace=',,t,d' sastraceloc=saslog stsuffix obs=1;
proc sql;
  create table x as select * from hoge.xyz;
quit;
options sastrace=off obs=max;

2022年5月9日月曜日

SAS マクロ変数 /READONLY

2015年の新機能ですが、見落としていました。
マクロ変数の値を READONLY にして定数化できるのね。

https://support.sas.com/resources/papers/proceedings15/SAS1575-2015.pdf

2022年5月4日水曜日

SAS Viya CLI 入門 / SAS Viya REST API 入門

SAS Viya CLIの入門として資料を書き起こしました。CLIを使ってViyaのシステムを管理したい人向けです。内容を以下に示します。

  • SAS Viya CLIのインストール
  • プロファイル設定
  • プラグインを使った操作
  • OAuthプラグインでクライアントの登録
  • SAS Viya CLI + jq
  • curl + SAS Viya REST APIs
  • SAS Viya REST API 最初に見るポイント
時間があるときに以下の項目を足そうかと考えています。
  • SASコンテンツのフォルダ作成と権限の設定

いやいや、動いているのを見たいんだよと言う方は下の動画をご覧ください。




2021年11月26日金曜日

JSONのファイルをPROC HTTPで読み込む

SASでJSONのファイルを読み込むのは面倒かと思っていましたが、JSONエンジンとAUTOMAPオプションを使うと簡単にデータセットとして中身を見ることが出来ます。とりあえず読み込んでから中味を確認して、あとは用途に合わせて表形式に加工します。この例はViyaのジョブフローのスケジューリングの情報を取ってきます。
/* ジョブスケジューリングのJSONファイルとマップのファイルを定義 */
filename folders temp;
filename jsonmap temp;

/* JSONファイルを取得 */
proc http 
		url="https://henteko.azure.com/jobFlowScheduling/flows" 
		out=flows oauth_bearer=sas_services;
	headers 'Accept'='application/vnd.sas.collection+json';
run;

/* JSONファイルの先頭をログに出力して確認するだけ */
data _null_;
	infile flows;
	length buf $4096;
	input buf 1-4096;
	put _all_;
run;

/* JSONエンジンで読み込ませて、マップは生成 */
libname flows json fileref=folders map=jsonmap automap=replace;

/* 後はライブラリ参照名から要素と中身を見る */
proc print data=flows.items;
run;
自分でマップファイルを作らなくても、JSONエンジン任せで構造を読み解いて参照できるのはすごく楽です。下はJSONファイルを読み込んだ例で、もちろん中身の定義で見え方は異なります。

2021年1月17日日曜日

タイムスタンプの文字列生成

よく使うけど、すぐに忘れるタイムスタンプの文字列 

%let dt=%sysfunc(putn(%sysfunc(datetime()), E8601DT20.));

2021-01-17T20:35:17


2021年1月8日金曜日

メタデータオブジェクトの検索例

 SAS9でメタデータサーバの情報を探し出して、リストを作ることは稀にあります。たまにしかやらないから、どんな検索式を与えるか忘れてしまいます。備忘としていくつか例を残してい置きます。メタデータのAPIは、情報を1個ずつとると遅いのでできれば検索式で絞り込んで少ない回数で取得した方が良いです。

配置済みのジョブの検索
JFJob?@id contains '.' and @PublicType = 'DeployedJob'

実行キューの名前からジョブフローを検索
omsobj:JFJob?JFjob[@id contains '.' ][Properties/Property[@DefaultValue='normal' and @Name='QUEUE']

トリガーの条件を設定している配置済みフローを検索
omsobj:JFJob?JFjob[@PublicType = 'DeployedFlow'][Steps/SyncStep/TriggeringEvents/Event]]

配置済みジョブフローのオブジェ口IDを指定してかつ、作成者を特定する検索式
omsobj:ResponsibleParty?ResponsibleParty[@Role='Created By'][Objects/JFJob[@id='A5MGMPLF.BZ001J44']]

プロパティの名前からメタデータオブジェクトを検索する例
omsobj:Property?@Name='SCHEDULINGDETAILS'

テーブルの名前をからメタデータオブジェクトを検索する例
PhysicalTable?@id contains '.' and @Name eq 'HMEQ'

2020年7月6日月曜日

スクリプトに組み込むための1行コード(one-liner)

忘れやすいのでメモ
1. Unixの場合には-stdio オプションで、コードを標準入力から投入できる。

echo "proc options option=dms value; run;" |  sas -nodms -stdio

2.  Libnameを書かなくても任意のディレクトリにSASデータセットを出力できる。

 echo "data '/var/tmp/foo.sas7bdat'; set sashelp.class; run;" | sas -stdio

2020年4月6日月曜日

LOCKDOWNで指定できるディレクトリを制限

セキュリティに関心のある人向けの投稿です。サーバクライアントの構成で、フォルダのアクセス制限をする方法を調べていたら、ありました。だいぶ昔、2014年の記事 ”Fencing in your SAS users with LOCKDOWN” なので、長いこと気が付いていなかった。

悪意あるユーザが、コードタスクでディレクトリのパスを探索すると、他の人、他の部署に見せたくないものが見えてしまう可能性があります。これを防止する手段が、LOCKDOWN システムオプションです。設定の方法は、前述の記事を参照してください。許可されていないディレクトリにLIBNAMEを割り当てようとすると、どのようなエラーが起きるか示します。

Workspaceサーバのディレクトリにある設定ファイルをいじって、LOCKDOWNのオプションと許可するパスを設定します。

3つのパターンでLIBNAMEを割り当てます。1つ目が、許可されていないディレクトリ、2つ目が許可したディレクトリ、3つ目が許可されていないディレクトリのサブディレクトリです。

 これを実行すると、1つ目がエラーになります。検索に引っ掛かるようにエラーメッセージをタイプしておきます。この投稿を書いている時点では、日本語のエラーメッセージはヒットしません。ERROR: SAS がロックダウン状態の場合、パスc:\tempはアクセス可能なパスのリストに含まれません。

このLOCKDOWNオプションで物理パスの指定を守り、アクセス権をより強固に縛るには、Metadata-Bound Libraries を使えとのことです。これを実運用で使っているところを知らないけど、単に私が無知なだけなのか?同僚に聞いてみよう。