test命令
每一種條件語(yǔ)句的基礎(chǔ)都是判斷什么是真什么是假。是否了解其工作原理將決定您編寫(xiě)的是質(zhì)量一般的腳本還是您將引以為榮的腳本。
Shell 腳本的能力時(shí)常被低估,但實(shí)際上其能力的發(fā)揮受制于腳本撰寫(xiě)者的能力。您了解得越多,您就越能像變戲法似地撰寫(xiě)一個(gè)文件來(lái)使任務(wù)自動(dòng)化和簡(jiǎn)化您的管理工作。
在 shell 腳本中進(jìn)行的每一種操作(除最簡(jiǎn)單的命令編組之外)都需要檢查條件。所有的 shell 腳本“邏輯” — 廣義意義下的“邏輯” — 通常都可以分為以下三大類:
if {condition exists} then ...
while {condition exists} do ...
until {condition exists} do ...
無(wú)論隨后的操作是什么,這些基于邏輯的命令都依靠判斷一種條件是否真實(shí)存在來(lái)決定后續(xù)的操作。test 命令是使得在每一種情況下都能夠確定要判斷的條件是否存在的實(shí)用工具。因此,徹底了解這個(gè)命令對(duì)于撰寫(xiě)成功的 shell 腳本至關(guān)重要。
工作原理
test 命令最短的定義可能是評(píng)估一個(gè)表達(dá)式;如果條件為真,則返回一個(gè) 0 值。如果表達(dá)式不為真,則返回一個(gè)大于 0 的值 — 也可以將其稱為假值。檢查最后所執(zhí)行命令的狀態(tài)的最簡(jiǎn)便方法是使用 $? 值。出于演示的目的,本文中的例子全部使用了這個(gè)參數(shù)。
test 命令期望在命令行中找到一個(gè)參數(shù),當(dāng) shell 沒(méi)有為變量賦值時(shí),則將該變量視為空。這意味著在處理腳本時(shí),一旦腳本尋找的參數(shù)不存在,則 test 將報(bào)告該錯(cuò)誤。
當(dāng)試圖保護(hù)腳本時(shí),您可以通過(guò)將所有參數(shù)包含在雙引號(hào)中來(lái)解決這個(gè)問(wèn)題。然后 shell 將變量展開(kāi),如果變量沒(méi)有值,那么將傳遞一個(gè)空值給 test。另一種方法是在腳本內(nèi)增加一個(gè)額外檢查過(guò)程來(lái)判斷是否設(shè)置了命令行參數(shù)。如果沒(méi)有設(shè)置命令行參數(shù),那么腳本會(huì)告訴用戶缺少參數(shù),然后退出。我們會(huì)通過(guò)一些例子來(lái)更具體地說(shuō)明所有這些內(nèi)容。
test 和 [ 命令
雖然 Linux 和 UNIX 的每個(gè)版本中都包含 test 命令,但該命令有一個(gè)更常用的別名 — 左方括號(hào):[。test 及其別名通常都可以在 /usr/bin 或 /bin (取決于操作系統(tǒng)版本和供應(yīng)商)中找到。
當(dāng)您使用左方括號(hào)而非 test 時(shí),其后必須始終跟著一個(gè)空格、要評(píng)估的條件、一個(gè)空格和右方括號(hào)。右方括號(hào)不是任何東西的別名,而是表示所需評(píng)估參數(shù)的結(jié)束。條件兩邊的空格是必需的,這表示要調(diào)用 test,以區(qū)別于同樣經(jīng)常使用方括號(hào)的字符/模式匹配操作。
test 和 [ 的語(yǔ)法如下:
test expression
[ expression ]
在這兩種情況下,test 都評(píng)估一個(gè)表達(dá)式,然后返回真或假。如果它和 if、while 或 until 命令結(jié)合使用,則您可以對(duì)程序流進(jìn)行廣泛的控制。不過(guò),您無(wú)需將 test 命令與任何其它結(jié)構(gòu)一起使用;您可以從命令行直接運(yùn)行它來(lái)檢查幾乎任何東西的狀態(tài)。
因?yàn)樗鼈儽舜嘶閯e名,所以使用 test 或 [ 均需要一個(gè)表達(dá)式。表達(dá)式一般是文本、數(shù)字或文件和目錄屬性的比較,并且可以包含變量、常量和運(yùn)算符。運(yùn)算符可以是字符串運(yùn)算符、整數(shù)運(yùn)算符、文件運(yùn)算符或布爾運(yùn)算符 — 我們將在以下各部分依次介紹每一種運(yùn)算符。
test 文件運(yùn)算符
利用這些運(yùn)算符,您可以在程序中根據(jù)對(duì)文件類型的評(píng)估結(jié)果執(zhí)行不同的操作:
-b file 如果文件為一個(gè)塊特殊文件,則為真
-c file 如果文件為一個(gè)字符特殊文件,則為真
-d file 如果文件為一個(gè)目錄,則為真
-e file 如果文件存在,則為真
-f file 如果文件為一個(gè)普通文件,則為真
-g file 如果設(shè)置了文件的 SGID 位,則為真
-G file 如果文件存在且歸該組所有,則為真
-k file 如果設(shè)置了文件的粘著位,則為真
-O file 如果文件存在并且歸該用戶所有,則為真
-p file 如果文件為一個(gè)命名管道,則為真
-r file 如果文件可讀,則為真
-s file 如果文件的長(zhǎng)度不為零,則為真
-S file 如果文件為一個(gè)套接字特殊文件,則為真
-t fd 如果 fd 是一個(gè)與終端相連的打開(kāi)的文件描述符(fd 默認(rèn)為 1),則為真
-u file 如果設(shè)置了文件的 SUID 位,則為真
-w file 如果文件可寫(xiě),則為真
-x file 如果文件可執(zhí)行,則為真
以下示例顯示了此簡(jiǎn)單操作的運(yùn)行情況:
$ ls -l
total 33
drwxr-xr-w 2 root root 1024 Dec 5 05:05 LST
-rw-rw-rw- 1 emmett users 27360 Feb 6 07:30 evan
-rwsrwsrwx 1 root root 152 Feb 6 07:32 hannah
drwxr-xr-x 2 emmett users 1024 Feb 6 07:31 karen
-rw------- 1 emmett users 152 Feb 6 07:29 kristin
-rw-r--r-- 1 emmett users 152 Feb 6 07:29 spencer
$
$ test -r evan
$ echo $?
0
$ test -r walter
$ echo $?
1
$
由于第一次評(píng)估為真 — 文件存在且可讀 — 返回值為真,或 0。由于第二次評(píng)估的文件不存在,該值為假,返回值不為零。將值指定為零或非零很重要,因?yàn)樵谑r(shí)不會(huì)始終返回 1(雖然這是通常返回的值),可能返回一個(gè)非零值。
正如開(kāi)頭所提到的,除了使用 test 外,您還可以用方括號(hào) [ ] 將命令括住來(lái)向 shell 發(fā)出同樣的命令 — 如下所示:
$ [ -w evan ]
$ echo $?
0
$ [ -x evan ]
$ echo $?
1
$
同樣,第一個(gè)表達(dá)式為真,第二個(gè)表達(dá)式為假 — 正如返回值所指示的那樣。您還可以使用以下命令將兩個(gè)文件彼此進(jìn)行比較:
file1 -ef file2 測(cè)試以判斷兩個(gè)文件是否與同一個(gè)設(shè)備相連,是否擁有相同的 inode 編號(hào)
file1 -nt file2 測(cè)試以判斷第一個(gè)文件是否比第二個(gè)文件更新(由修改日期決定)
file1 -ot file2 測(cè)試以判斷第一個(gè)文件是否比第二個(gè)文件更舊
以下示例顯示了使用這些運(yùn)算符比較文件的結(jié)果:
$ [ evan -nt spencer ]
$ echo $?
0
$ [ karen -ot spencer ]
$ echo $?
1
$
名為 evan 的文件比名為 spencer 的文件更新,因而評(píng)估為真。類似地,名為 karen 的文件比名為 spencer 的文件更新,因此該評(píng)估為假。
字符串比較運(yùn)算符
如標(biāo)題所示,這組函數(shù)比較字符串的值。您可以檢查它們是否存在、是否相同或者是否不同。
String 測(cè)試以判斷字符串是否不為空
-n string 測(cè)試以判斷字符串是否不為空;字符串必須為 test 所識(shí)別
-z string 測(cè)試以判斷字符串是否為空;字符串必須為 test 所識(shí)別
string1 = string2 測(cè)試以判斷 string1 是否與 string2 相同
string1 != string2 測(cè)試以判斷 string1 是否與 string2 不同
對(duì)任何變量進(jìn)行的最有用的測(cè)試之一是判斷它的值是否不為空,可以簡(jiǎn)單地將其放在 test 命令行中執(zhí)行這種測(cè)試,如下例所示:
$ test "$variable"
強(qiáng)烈建議進(jìn)行此種測(cè)試時(shí)用雙引號(hào)將變量括住,以讓 shell 識(shí)別變量(即使變量為空)。默認(rèn)情況下執(zhí)行的基本字符串評(píng)估和 -n 測(cè)試從功能上講是相同的,如以下示例所示:
#example1
if test -n "$1"?
then
echo "$1"?
fi
執(zhí)行以上例子中的代碼將根據(jù) $1 是否存在給出以下結(jié)果:
$ example1 friday
friday
$
$ example1
$
如果將代碼更改為以下形式,則結(jié)果將相同:
#example2
if test "$1"?
then
echo "$1"?
fi
如下所示:
$ example2 friday
friday
$
$ example2
$
所有這些表明,通常不需要 -n,它代表默認(rèn)操作。
要從一個(gè)不同的角度來(lái)查看各種可能性,您可以用另一個(gè)選項(xiàng)來(lái)替換 -n,并檢查該值是否為空(相對(duì)于非空)。這可以用 -z 選項(xiàng)來(lái)實(shí)現(xiàn),代碼為:
#example3
if test -z "$1"?
then
echo "no values were specified"?
fi
運(yùn)行如下:
$ example3?
no values were specified?
$ example3 friday
$?
如果在沒(méi)有命令行參數(shù)的情況下運(yùn)行該程序,而表達(dá)式評(píng)估為真,那么將執(zhí)行程序塊中的文本。如果在命令行中有值,則腳本退出,不執(zhí)行任何操作。將評(píng)估操作放在腳本的開(kāi)頭非常有用,這可以在可能產(chǎn)生錯(cuò)誤的進(jìn)一步處理之前預(yù)先檢查變量值。
其余的字符串運(yùn)算符對(duì)兩個(gè)變量/字符串之間的精確匹配或其中的差異(您也可以稱之為等價(jià)性和“不等價(jià)性”)進(jìn)行評(píng)估。第一個(gè)例子對(duì)匹配進(jìn)行測(cè)試:
$ env
LOGNAME=emmett
PAGER=less
SHELL=/bin/bash
TERM=linux
$
$ [ "$LOGNAME" = "emmett" ]
$ echo $?
0
$
$ [ "$LOGNAME" = "kristin" ]
$ echo $?
1
$
或者,該評(píng)估可以以腳本的形式用于決定是否運(yùn)行腳本:
#example4
if [ "$LOGNAME" = "emmett" ]
then
echo "processing beginning"
else?
echo "incorrect user"
fi
這種方法可以用來(lái)尋找任意的值(如終端類型或 shell 類型),在允許腳本運(yùn)行之前這些值必須匹配。請(qǐng)注意,= 或 != 運(yùn)算符的優(yōu)先級(jí)高于其它大多數(shù)可指定選項(xiàng),且要求必須伴有表達(dá)式。因此,除了比較字符串的選項(xiàng)之外,= 或 != 都不能和檢查某種東西(如可讀文件、可執(zhí)行文件或目錄)的存在性的選項(xiàng)一起使用。
整數(shù)比較運(yùn)算符
正如字符串比較運(yùn)算符驗(yàn)證字符串相等或不同一樣,整數(shù)比較運(yùn)算符對(duì)數(shù)字執(zhí)行相同的功能。如果變量的值匹配則表達(dá)式測(cè)試為真,如果不匹配,則為假。整數(shù)比較運(yùn)算符不處理字符串(正如字符串運(yùn)算符不處理數(shù)字一樣):
int1 -eq int2 如果 int1 等于 int2,則為真
int1 -ge int2 如果 int1 大于或等于 int2,則為真
int1 -gt int2 如果 int1 大于 int2,則為真
int1 -le int2 如果 int1 小于或等于 int2,則為真
int1 -lt int2 如果 int1 小于 int2,則為真
int1 -ne int2 如果 int1 不等于 int2,則為真
以下示例顯示了一個(gè)代碼段,其中在命令行中給出的值必須等于 7:
#example5
if [ $1 -eq 7 ]
then
echo "You've entered the magic number."
else?
echo "You've entered the wrong number."
fi
運(yùn)行中:
$ example5 6
You've entered the wrong number.
$
$ example5 7
You've entered the magic number.
$
和字符串一樣,比較的值可以是在腳本外為變量賦的值,而不必總是在命令行中提供。以下示例演示了實(shí)現(xiàn)這一點(diǎn)的一種方法:
#example6
if [ $1 -gt $number ]
then
echo "Sorry, but $1 is too high."
else?
echo "$1 will work."
fi
$ set number=7
$ export number
$ example6 8
Sorry, but 8 is too high.
$ example6 7
7 will work.
$
整數(shù)比較運(yùn)算符最佳的用途之一是評(píng)估指定的命令行變量的數(shù)目,并判斷它是否符合所要求的標(biāo)準(zhǔn)。例如,如果某個(gè)特定的命令只能在有三個(gè)或更少變量的情況下運(yùn)行,
#example7 - display variables, up to three
if [ "$#" -gt 3 ]
then
echo "You have given too many variables."
exit $#
fi
只要指定三個(gè)或更少的變量,該示例腳本將正常運(yùn)行(并返回值 0)。如果指定了三個(gè)以上的變量,則將顯示錯(cuò)誤消息,且例程將退出 — 同時(shí)返回與命令行中給定的變量數(shù)相等的退出代碼。
對(duì)這個(gè)過(guò)程進(jìn)行修改可以用來(lái)在允許運(yùn)行報(bào)表之前判斷當(dāng)天是否是本月的最后幾天:
#example8 - to see if it is near the end of the month#
set `date` # use backward quotes
if [ "$3" -ge 21 ]
then
echo "It is close enough to the end of the month to proceed"
else?
echo "This report cannot be run until after the 21st of the month"?
exit $3
fi
在這個(gè)例子中,設(shè)置了六個(gè)變量(通過(guò)空格彼此分開(kāi)):
$1 = Fri
$2 = Feb
$3 = 6
$4 = 08:56:30
$5 = EST
$6 = 2004
這些值可以在腳本中使用,就像它們是在命令行中輸入的一樣。請(qǐng)注意,退出命令再次返回一個(gè)值 — 在這種情況下,返回的值是從 $3 的值中得到的日期。這一技巧在故障診斷時(shí)會(huì)非常有用 — 如果您認(rèn)為腳本應(yīng)該運(yùn)行而沒(méi)有運(yùn)行,那么請(qǐng)查看 $? 的值。
一種類似的想法可能是撰寫(xiě)一個(gè)只在每個(gè)月的第三個(gè)星期三運(yùn)行的腳本。第三個(gè)星期三一定在該月的 15 日到 21 日之間。使用 cron,您可以調(diào)用腳本在 15 日到 21 日之間每天的一個(gè)指定時(shí)間運(yùn)行,然后使用腳本的第一行檢查 $1(在設(shè)置日期之后)的值是否為 Thu。如果為 Thu,那么執(zhí)行剩下的腳本,如果不是,則退出。
而另一個(gè)想法可能是,只允許腳本在超過(guò) 6:00 p.m. (18:00),所有用戶都回家之后運(yùn)行。只要撰寫(xiě)腳本,使其在值低于 18 時(shí)退出,并通過(guò)使用以下命令來(lái)獲取時(shí)間(將其設(shè)為 $1)
set `date +%H`
布爾運(yùn)算符
布爾運(yùn)算符在幾乎每種語(yǔ)言中的工作方式都相同 — 包括 shell 腳本。在 nutshell 中,它們檢查多個(gè)條件為真或?yàn)榧?#xff0c;或者針對(duì)假的條件而不是真的條件采取操作。與 test 搭配使用的運(yùn)算符有
! expr 如果表達(dá)式評(píng)估為假,則為真
expr1 -a expr2 如果 expr1 和 expr2 評(píng)估為真,則為真
expr1 -o expr2 如果 expr1 或 expr2 評(píng)估為真,則為真
可以用 != 運(yùn)算符代替 = 進(jìn)行字符串評(píng)估。這是最簡(jiǎn)單的布爾運(yùn)算符之一,對(duì) test 的正常結(jié)果取非。
其余兩個(gè)運(yùn)算符中的第一個(gè)是 -a(即 AND)運(yùn)算符。要使測(cè)試最終為真,兩個(gè)表達(dá)式都必須評(píng)估為真。如果任何一個(gè)評(píng)估為假,則整個(gè)測(cè)試將評(píng)估為假。例如,
$ env
HOME=/
LOGNAME=emmett
MAIL=/usr/mail/emmett
PATH=:/bin:/usr/bin:/usr/lbin
TERM=linux
TZ=EST5:0EDT
$
$ [ "$LOGNAME" = "emmett" -a "$TERM" = "linux" ]
$ echo $?
0
$
$ [ "LOGNAME" = "karen" -a "$TERM" = "linux" ]
$ echo $?
1
$
在第一個(gè)評(píng)估中,兩個(gè)條件都測(cè)試為真(在一個(gè) linux 終端上登錄的是 emmett),因此整個(gè)評(píng)估為真。在第二個(gè)評(píng)估中,終端檢查正確但用戶不正確,因此整個(gè)評(píng)估為假。
簡(jiǎn)而言之,AND 運(yùn)算符可以確保代碼只在兩個(gè)條件都滿足時(shí)才執(zhí)行。相反,只要任何一個(gè)表達(dá)式測(cè)試為真,OR (-o) 運(yùn)算符即為真。我們來(lái)修改先前的例子,并將其放到一個(gè)腳本中來(lái)說(shuō)明這一點(diǎn):
#example9
if [ "$LOGNAME" = "emmett" -o "$TERM" = "linux" ]
then
echo "Ready to begin."
else?
echo "Incorrect user and terminal."?
fi
$ env
HOME=/
LOGNAME=emmett
MAIL=/usr/mail/emmett
PATH=:/bin:/usr/bin:/usr/lbin
TERM=linux
TZ=EST5:0EDT
$ example9
Ready to begin.
$
$ LOGNAME=karen
$ example9
Ready to begin.
$
在腳本第一次運(yùn)行時(shí),評(píng)估判斷用戶是否等于 emmett。如果發(fā)現(xiàn)用戶等于 emmett,則腳本轉(zhuǎn)至 echo 語(yǔ)句,并跳過(guò)其余的檢查。它從不檢查終端是否等于 linux,因?yàn)樗恍枰业揭粭l為真的語(yǔ)句就可以使整個(gè)運(yùn)算為真。在腳本第二次運(yùn)行時(shí),它判斷用戶不是 emmett,因此它將檢查并發(fā)現(xiàn)終端確實(shí)是 linux。由于一個(gè)條件為真,腳本現(xiàn)在轉(zhuǎn)至 echo 命令。為了引出第二條消息,兩個(gè)條件都必須為假。
在先前確定時(shí)間是否為月末的例子中,可以執(zhí)行類似的檢查來(lái)防止用戶試圖在周末運(yùn)行腳本:
#example10 - Do not let the script run over the weekend#
set `date` # use backward quotes
if [ "$1" = "Sat" -o "$1" = "Sun" ]
then
echo "This report cannot be run over the weekend."?
fi
一些有用的示例
示例 1:在腳本文件中出現(xiàn)的“邏輯”的最簡(jiǎn)單的形式(如本文所有示例中所示)是“if ... then”語(yǔ)句。先前的一個(gè)代碼段檢查是否存在一定數(shù)量的變量,然后將這些變量回顯。假設(shè)我們對(duì)此稍微做一些修改,比如我們想回顯變量,并且每次回顯均減去最左邊的變量,以顯示一個(gè)倒的三角形。
雖然這聽(tīng)起來(lái)很簡(jiǎn)單,但實(shí)際并非如此;這是您在執(zhí)行大規(guī)模處理時(shí)想實(shí)現(xiàn)的方式:處理第一個(gè)變量、轉(zhuǎn)移、處理下一個(gè)變量……
出于演示的目的,可以按以下方式撰寫(xiě)腳本中的重要行:
#example11 - display declining variables, up to three
if [ "$#" -gt 3 ] # see if more than three variables are given
then
echo "You have given more than three variables."?
exit
fi
echo $*
if test -n "$2"
then
shift
echo $*
fi
if test -n "$2"
then
shift
echo $*
fi
它將按以下方式執(zhí)行:
$ example11 one
one
$
$ example11 one two
one two
two
$
$ example11 one two three
one two three
two three
three
$
$ example11 one two three four
You have given more than three variables.
$
出于檢查的目的將數(shù)量限制為三個(gè)變量的原因是減少在例子中要檢查的行數(shù)。一切都按部就班地進(jìn)行,雖然它令人難以置信地混亂;用戶因使用了超過(guò)程序依設(shè)計(jì)所能處理的變量數(shù)而得到警告,且腳本退出。如果變量數(shù)為 3 或更少,則運(yùn)算的核心部分開(kāi)始執(zhí)行。
回顯變量,執(zhí)行測(cè)試以查看另一個(gè)變量是否存在。如果另一個(gè)變量存在,則執(zhí)行一次轉(zhuǎn)移,回顯該變量,執(zhí)行另一測(cè)試,等等。總共使用了 16 個(gè)有效行,而程序僅能處理不超過(guò)三個(gè)變量 — 非常混亂。假設(shè)消除變量數(shù)的限制,程序可以處理任意數(shù)量的變量。經(jīng)過(guò)一些修改,腳本被縮短(美化)了,并能處理任意數(shù)量的變量:
#example12 - display declining variables, any number
while [ "$#" -gt 0 ]
do
echo $*
shift
done
$ example12 1 2 3 4 5 6 7 8 9 0
1 2 3 4 5 6 7 8 9 0
2 3 4 5 6 7 8 9 0
3 4 5 6 7 8 9 0
4 5 6 7 8 9 0
5 6 7 8 9 0
6 7 8 9 0
7 8 9 0
8 9 0
9 0
0
現(xiàn)在減少到只有 5 個(gè)有效行,且消除了第一個(gè)腳本三個(gè)變量的限制,并在運(yùn)行時(shí)要更高效。
示例 2:無(wú)論何時(shí)當(dāng)在腳本內(nèi)執(zhí)行與處理相關(guān)的操作時(shí),下一個(gè)操作將始終檢查上一操作的狀態(tài),以確認(rèn)它已成功完成。您可以通過(guò)檢查 $? 的狀態(tài)并驗(yàn)證它等于 0 來(lái)實(shí)現(xiàn)這一目的。例如,如果一個(gè)數(shù)據(jù)目錄是否能訪問(wèn)非常重要,
#example13
TEMP=LST
cd $TEMP
if [ $?-ne 0 ]
then
echo "Data directory could not be found."?
Exit
fi
test 命令常常出現(xiàn)的錯(cuò)誤事實(shí)上只有兩種類型。第一種是未使用正確的評(píng)估類型,例如將字符串變量與整型變量進(jìn)行比較或者將帶填充的字符串與不帶填充的字符串進(jìn)行比較。仔細(xì)評(píng)估您使用的變量將使您最終找到錯(cuò)誤的根源,并讓您能夠解決這些問(wèn)題。
第二種錯(cuò)誤類型包括將方括號(hào)誤認(rèn)為別名之外的某個(gè)東西。方括號(hào)與其內(nèi)容之間必須有一個(gè)空格;否則,它們將不能解釋其中的對(duì)象。例如,
$ [ "$LOGNAME" -gt 9]
test:] missing
$
請(qǐng)注意,錯(cuò)誤消息指示 test 存在問(wèn)題,即使使用了別名 ]。這些問(wèn)題很容易發(fā)現(xiàn),因?yàn)殄e(cuò)誤消息準(zhǔn)確地將這些問(wèn)題顯示出來(lái),然后您可以增加必要的空格。
結(jié)論
要在 shell 腳本中構(gòu)建邏輯,您必須添加條件語(yǔ)句。每一條這種語(yǔ)句的核心都是對(duì)條件的評(píng)估,以判斷它是否存在 — 通過(guò)使用 test 命令完成評(píng)估。了解它和它的別名(左方括號(hào) ([)的工作原理將使您能夠撰寫(xiě)可以完成一些復(fù)雜操作的 shell 腳本。
轉(zhuǎn)載于:https://www.cnblogs.com/bethal/p/5353870.html
總結(jié)
- 上一篇: hdu 1176 dp 数塔问题
- 下一篇: Nodejs in Visual Stu