一文掌握cshell编程_1(概念、案例双管齐下)

前言

相信在IC行业中不少公司使用cshell作为脚本语言之一,很不幸的是目前网络上cshell的中文介绍少之又少,绝大部分是bash的介绍。近期有幸阅读了一本cshell cookbook,故将个人的学习心得发布于此,也希望志同道合的朋友一起探讨一番。(从看cookbook到博文的发表历时4个月,每天下完班就瞌睡的很,icer sandman无疑了,当然也是创作不易呀)。
下面我将从cshell的变量,字符处理,文件处理和基本的语句结构进行介绍。本文假设读者对cshell脚本一无所知,sandman力求写一篇白菜教程。因此,仅仅需要读者了解如何跑cshell的脚本即可,即terminal中输入source xxxx.csh。现在知道也不迟的,详细仔细阅读这篇文章对于编写或者修改cshell脚本有较大的帮助。

shell selection(shell类型选择)

通常在cshell脚本的第一行,我们需要指明脚本的类型,这有助于UNIX系统去解析对应的脚本。换句话说,系统需要知道用户source的脚本是哪一种类型的,是perl,bash,cshell还是TCL(后续sandman会慢慢的更新,但是不知道何时有空更新,随缘吧)

#!/bin/csh   #注释:正常情况下写这一条代码就可以了,因为bin目录下一般会存在csh。

有了前面的铺垫后,下面开始进入正题:

变量

cshell的变量可以分为标量,数组和特殊变量。

  1. 标量(scalar)
    set是给标量赋值的关键字。cshell中没有字符串,整型,负数以及浮点型的区别,统一使用set为标量赋值,下面看几个例子。
set colour = blue
set title = "hello world"
set float_dat = 1.23E-09
set int_dat = 9
set int_dat_s = -9

由此可知,设置变量的范式如下所示。

set VAR_NAME = VALUE #VAR_NAME: 标量名称;VALUE:标量值

另外,在某些情况下可能需要移除某个标量,被移除的标量不能在后续的程序中使用。cshell脚本中使用unset便可实现。如移除colour。

set colour = blue
#标量参与了某件事情的处理
unset colour
#由于使用unset移除了colour,因此后续不能在使用 colour这个标量

值得注意的是:unset支持字符匹配的方式,表示匹配的标量都会被移除,如下所示。

unset iso_* #表示移除名称是以iso_为前缀的所有标量,如iso_a,iso_1, iso_1234等。

另外,可能还有setenv这个关键字,该关键字表示设置系统标量,即整个系统都可以看见。移除某个系统标量的关键字为unsetenv。那么两者的区别是什么呢?setsetenv两者的区别为:可见的范围不同。

  • set定义的标量仅仅是当前脚本可见。
  • setenv定义的变量是系统可见,可以用echo输出系统标量,即echo $SYS_VAR_NAME

个人建议少用系统标量,要用也应该避免和系统中以后的标量名重复。即,假设系统中有一个标量的名字为SYS,如果我们在编写xxx.csh脚本时,一步小心写setenv SYS = 1, 那么source xxx.csh后,原本的SYS将变成1,覆盖了之前的值,这可能是灾难性的后果。

这里介绍一个使用系统标量的实际案例。有些场景中需要在cshell中调用TCL,并且不是直接调用,中间可能还包了一层或者几层的其他脚本,如Makefile。然而,我们需要把cshell中的某个标量值传递给TCL,为了简单起见且修改最少的内容,我们可以把需要传递的标量定义为系统标量,这样在TCL中我们就可以直接使用。

setenv SWCH_ME = "ON" # 定义系统标量SWCH_ME
# 下面是TCL语句
set SWCH = $ENV{SWCH_ME} #TCL中引用系统标量SWCH_ME

我想关于标量的描述有这些就够了。

  1. 数组(array)
    数组可以简单的理解为多个标量组合。即通过括号将多个标量组合起来,并且使用空格作为分隔符。其中,一个数组里面可以包含不同类型的标量,如整型,浮点型,字符型等。另外如果数组中包含了带空格的字符串,那么一定需要使用双引号将字符串包起来,否则字符串会被解析器认为是数组中的多个元素。简单示例如下所示。
set coluors = (blue yellow green red pink)
set misc = ("hello world" blue 3 -4 1.34E09)

其中,hello world不采用“”包起来的话,那么解析器就会认为hello是数组misc的一个元素,world是数组的另一个元素。
数组的范式如下所示:
在这边插一个打印数组的方式,在debug期间经常会使用,当然后文会有较为详细的介绍。

echo "info coluors array: $coluors"
echo "info misc array: $misc"
set ARR_NAME = (ELEM1 ELEM2 ELEM3)

定义好数组后,更加关键的是如何使用。下面以一个实例详细的介绍数据的使用。
定义一个数组prime:

set prime = (1 2 3 4 5 6 7 8 9)
# $#prime的结果为9;
# $prime[*]的结果为:1 2 3 4 5 6 7 8 9;
# $prime[$]的结果为:9;
# $prime[3-5]的结果为:3 4 5;
# $prime[8-]的结果为:8 9

总结如下所述:

  • $#ARR_NAME表示数组的长度,即数据包含几个元素。
  • $ARR_NAME[*]表示数组的所有元素
  • $ARR_NAME[$]表示数组的最后一个元素。[Note: $通常都是用来表示最后一个object]
  • $ARR_NAME[M-N]表示数组的第M到第N个元素。其中,数组的索引下标是从1开始,而不是0;“-”是连接符。
  • $ARR_NAME[M-]表述数组的第M到最后一个元素。

某些场景下会使用到数组的拼接。如ARR1和ARR2拼接获得ARR3。如下所示:

set ARR1 = (1 2 3)
set ARR2 = ("hello world" blue)
set ARR3 = ($ARR2 $ARR1) # ARR3的结果为"hello world" blue 1 2 3

上述实例仅仅展示两个数组的拼接(应用了数组的所有元素),当然还可以选择数组的有个元素进行拼接。如下所示:

set ARR1 = (1 2 3 4 5 6)
set ARR2 = ("hello world" blue red yellow green)
set ARR3 = ($ARR2[1] $ARR1[3] $ARR1[4]) # ARR3的结果为 1 red 4
  1. 特殊字符(special characters)
    cshell定义了一些特殊的字符,或者称为元字符。如:*, ?, [],‘’, “”, **()**以及 ****。下面将逐个介绍:
  • *:通配符之一,表示任意多个字符。通常和unset搭配使用,如:unset iso_*(移除iso_为前缀的所有标量)
  • ?: 通配符之一,表示任意一个字符。通常和unset搭配使用,如:unset iso_?(移除iso_为前缀且"_"后只有一个字符的标量)
  • []: 用于数组的索引。如:ARR1[1](索引数组的第一个元素)
  • ‘’(单引号):用于定义字符串(不推荐使用),如:‘hello world’。另外``(在键盘~位置,即ESC下方的按键)用于包装命令,即让某些命令先执行(比较常见的一种使用方式)。如:set VAR = `echo $usr` 表示将标量usr的值赋值给标量VAR。其中usr为系统预定义的标量,后面将会介绍。
  • “”(双引号):用于定义字符串,如"hello world"。
  • (): 用于定义数组或者其他的语句结构,如if…then结构
  • \: 1. 转义元字符,如字符串中想表示*(星号),则采用\*。2. 用于长命令的连接,如一条命令太长了,想要分两行写,目的是便于阅读。其中,所有的元字符都可以使用“\”进行转义。

另外:特别说明一下单引号和双引号定义定义字符串是的区别。如下所示:

set five = 5
set VAR  = 'Give me $five' # VAR的结果为:Give me $five
set VAR2 = "Give me $five" # VAR2的结果为:Give me 5

明白了吧,换句话说,**单引号内的部分的元字符不具有特殊含义,双引号内的元字符仍旧具有特殊的含义。**我个人不建议使用单引号定义字符串,没有必要为难自己。

  1. 特殊变量(special variable)
    特殊变量顾名思义具有特殊含义的标量(变量)。在cshell中我们更加关心的是source脚本时跟随的参数以及脚本的名字。即脚本跟随了几个参数,分别是什么。如:
source run.csh -seed 20230727 -cover on -dump off

其中,"-seed 20230727 -cover on -dump off"都是脚本run.csh跟随的参数。下面我将结合上述的实例进行介绍。

  • ${0}: 脚本的名字。即,run.csh(♥♥♥);
  • $?VAR_NAME: 判断标量是否被定义,若定义,则返回结果为1,否则结果为0(♥♥♥♥);
  • $n: 脚本跟随的第n个参数(♥♥)。如$1表示-seed;$2表示20230727;
  • $argv[n]: 同上(♥)。
  • $#argv: 脚本跟随参数的个数(♥♥♥♥)。即,6个参数;
  • $*: 包含脚本名称以及脚本跟随的所有参数(♥)。即,7个参数。
  • $$: 脚本的进程号。一般作为文件的时间戳(♥♥♥)。
    需要特别提醒的是
  • ♥表示友好程度。
  • 脚本是在terminal中source的,但是上述提到的特殊变量是在脚本文件中直接使用的。当然有些特殊标量不好用($n,$argv[n])。为什么呢?你想啊,由上述可知每个参数的位置固定,不然就极有可能导致脚本出现非预期的结果。例如:
source run.csh -seed 20230727 -cover on -dump off
source run.csh -cover on -dump off -seed 20230727

显然,两个的$1不同,第一个的$1是"-seed";第二个的$1是"-cover"。若脚本中使用了$1那么不就存在问题了嘛。更合理的场景是参数的位置不需要固定,以及个数也需要固定(当然不能多于预定义的数量)。这个实现方式我们先按下不表,后续会有相应的介绍。

  1. cshell 预定义的变量(predefine variable)
    cshell中预定了部分变量,我们自己写脚本是应该要避免这些变量的定义。
set user    = "sandman" # 不推荐
set user_me = "sandman" # 推荐
  • cwd: 当前所在的目录
  • home: home目录
  • status: 最后一条命令的执行状态。0:完成;1:存在错误或者异常。
  • user:用户名
    个人感觉status和user比较有用。不过一般不太用得上。

字符串

cshell中字符串的处理往往可以借助三剑客(sed, grep, awk),感兴趣的朋友可以自行学习。三剑客的内容不是本文讨论的重点。这边介绍一下常用的字符处理手段,字符串拼接和拆分。

  • 时常出现需要将字符串拼接的操作,如将多个路径相对路径拼接成绝对路径。
set root_my = "/usr"
set user_my = "sandman"
set abs_dir = "$root_my/$user_my"  # 拼接两个字符
echo "info user path: $abs_dir" # 结果为:info user path: /usr/sandman 
  • 同样也时常需要将字符串按照某种格式进行拆分,如将时间拆分成时、分、秒,并且存放于数组中。这里借用了awk的split函数,所以说呀cshell的字符处理离不开三剑客。
set time = 12:34:56
set hms_arr = `echo $time | awk '{split($0, a, ":"); for(i=1; i<3; i++) print a[i]}' `

其中,split的函数格式为split(s, a, sep) s表示需要替换的字符串,a表示拆分结果存放的数组,sep表示按规定的符号拆分。对应上述的实例,则是$0代表$time(12:34:56) ; a表示拆分结果存放在数组a中;":“表示字符串按照”:"的形式拆分。值得注意的是最外层的反引号(`)用于封装等号右边的整体;单引号的作用域是”{}“(花括号)的语句;其本质是对语句做一个层次上的区分,提高可读性,也是awk语句不可缺少的一部分;花括号内的分号(;)表示语句的分割,这个Verilog等一众语言的作用一致。

文件处理

在cshell脚本中通常需要对文件进行处理,如获取文件的类型,文件名,路径以及带类型的文件名。以路径**/project/sandman/ug/cshell.docx**为例详细的解释上述的用法:

  • :e 获取文件类型。结果为:docx
  • :r 获取文件名字。结果为:cshell
  • :h 获取路径。结果为:/project/sandman/ug/
  • :t 获取文件类型和文件名。结果为:cshell.docx

其中,需要注意的是,“:e”和“:r”都是以句号(.)作为判断依据,若无句号,则“:e”返回空字符,“:r”返回完整的路径。如**/project/sandman/ug/cshell**,则“:e”返回的结果是空字符串;“:r”返回的结果是:/project/sandman/ug/cshell

另外,有时我们还需要对文件一些逻辑判断,这显然更加常用。如判断文件是否存在,是否为空文件,是否为目录等等。下面展示文件逻辑判断使用的选项。

  • -d 是否是目录。若为真,则为目录。
  • -e 是否存在文件。若为真,则存在文件。
  • -f 是否为普通文件。若为真,则是普通文件。(如何区别特殊文件和普通文件,我也不太清楚,有懂的大佬帮忙补充一下,多谢)
  • -o 我是否是文件的owner。若为真,则是owner。
  • -r 我对文件的属性是否是可读。若为真,则可读。
  • -w 我对文件的属性是否是可写。若为真,则可写。
  • -x 我对文件的属性是否是可执行。若为真,则可执行。
  • -z 文件是否是空文件。若为真,则文件是空文件。

以上就是对文件处理的常用操作了。

基本语法结构

下面我将介绍一些cshell中常见且重要的语句,这些语句在编写脚本时95%都用的上。将从最基础的打印字符开始介绍;并且随即深入到条件判读,循环结构以及switch的介绍;最后将给出一个完整的案例。相信了解本章之后对于编写简单甚至说复杂脚本都是足够的。简单提一句我个人对于cshell脚本是哦那个的看法,我个人觉得cshell用来做一些顶层的控制,即cshell脚本作为其他脚本的顶层。理由有以下两点:

  • cshell脚本可以认为是在command line(terminal)中输入多行命令,因此其他脚本可以在cshell脚本中轻松的被调用;
  • cshell脚本还可以调用系统提供的command,如具有UNIX三剑客之称的sed/grep/awk

其中,其他脚本可以是TCL类型的,Perl类型的,Makefile类型的,python类型的。

echo

echo类似于Perl的say,systemverilog的display。其本质是输出信息到屏幕或者指定的文件。其中,echo默认是带换行符的。所以说它类似Perl的say。下面举几个例子说明echo的用法。在实际中这个语句是非常好用的。

echo "hello world" # 打印"hello world"到屏幕,用于通知用户或者debug
echo "hello world" > log.txt # 打印"hello world"到log文件
echo "five plus five: $ans" #打印5+5的结果到屏幕,通常用于debug。

if

if语句是最常用的。if的语法结构如下所示:

if(condition) then
   statement
endif

含义:如果condition为真,那么执行statement,否则不执行。最后,特别提醒,if语句结构中的thenendif,这两个一定不能少,初学者需要特别的注意,并且statement后面是不带分号的,这个cshell的分割符有关系,可以简单的记忆为cshell是通过换行进行命令或者代码分割的。

if…else

if…else语句也是相当常见的。语法结构如下所示:

if(condition) then
   statement1
else
   statement2
endif

含义:如果condition为真,则执行statement1;如果condition是非真(即假),则执行statement2。

if…else if … else

有时我们还会使用else if的情况,从另一个角度来说if…else if…else是if语法中最复杂的结构了。语法结构如下所示:

if(condition1) then
   statement1
else if(condition2)
   statement2
else
   statement3
endif

含义:如果condition1为真,则执行statement1;如果仅仅condition2为真,则执行statement2;如果condition1和condition2都为假,则执行statement3。其中,仅仅condition2为真,表示condition1为假。比如说,condition1和condition2同时为真。从程序的执行角度来看,会进入第一级判读,因为condition1为真,所以会执行statement1(这点毫无疑问),但是condition2也为真,那么statement2会被执行吗?答案是不会。理由:if语句中,只要有条件成立则执行对应的语句,执行完后退出if。简单来说就是statement执行完了就会退出if语句,所以statement2不会被执行。

循环

前文中对if语句进行了较为详细的介绍,下面我们将详细的介绍循环语句,分别是while和foreach,请注意是foreach而不是for,在我的记忆中cshell中没有for。

while

某些场景下,我们希望某个事件或者条件为真时始终执行某个程序块(block or statement),事件或者条件为假时不在执行。这种场景下我们首选while循环。比如:我们需要指定statement执行的次数,即执行的次数已知。语法结构如下所示:

while (condition)
  statement
end

含义:首先进行condition的判断,若condition为真,则执行statement,当statement执行完成后,认为while循环第一次执行完成;随后,进行第二次condition的判断,若condition仍旧为真,则再一次执行statement,当statement执行完成后,认为while循环第二次执行完成;此后再次进行condition的判断,若condition为假,则直接退出while循环。
从上面的表述中可以清晰的看出,while循环存在始终执行的情况,或者称为死循环,即condition始终为真。通常我们需要写死循环时,condition为1即可。当然condition为100也可以,因为在cshell在解析condition时认为他是一个逻辑判断,从逻辑真假来看,非0即为真,仅仅是0是才会有假。
下面列举一个采用while循环执行循环次数的情况,其中涉及到数值计算写法,这个需要读者注意。

set cnt = 0
set sum = 0
while ($cnt <3)
  @ sum = $sum + $cnt
  @ cnt = $cnt + 1
end
echo "the sum is $sum" # 答案是0+1+2=3

上述的代码块介绍了一个非常简单的累加的功能,这个示例主要是想展示while循环和cshell中的数值计算。下面我们将详细的剖析这个过程:
首先,定义了cnt和sum两者标量,并且赋值为0,即初始化标量。
其次,判断while中的condition,因为0<3,所以condition为真,执行while内的语句。此时进行数值计算,sum为0,cnt为1。请注意数值计算的格式。抽象的格式如下所示:

@ VAR_NAME = $VAR_NAME +/-/*// $VAR_NAME(or constant) # @和VAR_NAME之间存在空格。

再者,再次判断while中的condition,因为1<3,所以condition为真,再次执行while内的语句,执行结果为sum为0+1=1,cnt为2;
随后,再次判断while中的condition,因为2<3,所以condition为真,再次执行while内的语句,执行结果为sum为0+1+2=3,cnt为3;
最后,,再次判断while中的condition,因为3<3,所以condition为退出while循环。
当然while还有一个更加重要的用法,也是最常用的用法之一,就是搭配switch语句,这个将在文末中给出,这边先按下不表。

foreach

还有一种非常重要且好用的循环结构foreach,顾名思义,遍历。那么它将会便利谁呢,显然是具有多个元素的内容,那前面介绍过的数组含有多个元素。对咯,foreach一般和数组搭配使用。不过从抽象的角度来看,foreach就是遍历容器中的所有元素,这就表明我不需要循环的此时,也不需要指定循环的次数,反正它会帮我们都执行一遍。 语法结构如下所示:

foreach SUB_ELEMENT (LIST)
  statement
end

含义:$SUB_ELEMENT为LIST中的某个元素,例如:LIST为(1 2 3 4)那么$SUB_ELEMENT将分别为1, 2, 3, 4,且对应每次循环。如第一次循环对应的值为1, 第二次循环对应的值为2。当然,LIST中可以是字符。如果LIST中只有3个元素,那么循环只执行三次,如果LIST有100个元素,那么循环就会执行100次。我觉的一个很典型的场景就是,验证人员可能会遇到这种情况,随着验证的不断深入testlist会不断的变大,比如group1由原来的2只case,变成20只case,并且这些case可能还会继续增加,但是在跑回归时,现有Group1的所有case都是要跑一遍(即遍历)的,我们就可以借助foreach循环来进行跑回归。伪代码如下所示:

foreach sub_case (group1)
  run sub_case
end

其中,run xxx表示执行某只case。run是封装好的cshell或者是其他脚本,sub_case是传入的参数。其中有一点需要了解的是cshell没有子函数一说,如果你需要实现子函数的功能,那你只能重新写一个cshell的脚本,把对应的功能封装起来。

switch

下面我们来聊一聊switch语句,switch语句可以认为是if语句的加强版。通常条件判断(condition)较多时选择使用switch,这样会使代码看起来更简洁,并且可读性更强。至于选择switch还是if完全取决于个人喜好,不过对于command line传递参数的场景,我个人建议使用switch。switch语句可以认为是一个逐个匹配的过程,直到匹配成功,当然可以匹配失败。例如:我只有一把钥匙,我尝试用这把钥匙去打开五间房间中的其中一间或者多间(一般是一间),并在打开后那一个物件。当然也可能一间房间也打不开。那我首先先尝试用这把钥匙去开第一间房间,发现打不开;那么我就尝试第二间房间,如果还是打不开,那我就继续往下尝试;如果到第四间房间的时候我打开了,那么我就进房间(即执行相应的代码);如果没有使用breaksw,则退出第四间房间后会继续尝试开第五间房间。通常switch和breaksw是一起使用的,即不会尝试开第五间房间。switch…endsw语句的结构如下所示:

switch(condition)
  case C1:
    statement1
  breaksw
  case C2:
    statement2
  breaksw
  default:
    statement_default
  breaksw
endsw

其中,condition就是钥匙,C1,C2,default就是房间。statement就是房间我需要的物件(羽毛球拍),即需要执行的代码。breaksw表示执行完对应的statement后退出switch语句,若没有这会继续往下匹配。default表示condition和C1、C2匹配不上时一定进入default分支。需要注意的时swich语句带有一个结尾的关键字endsw。具体的实例将在最后一章节中给出,融合while和shift一起介绍,也是switch…endsw最常见的应用场景之一。

shift

shift顾名思义,移动。操作的对象时数组,将数组的元素从左往右一个接一个的移出数组。语句结构如下所示:

set ARR1 = (1 2 3)
shift ARR1 # 数组中在的内容为2 3,1被移出数组

通常command line传入脚本的参数会被作为数组对待,因此常见的使用场景就是对参数做移动操作。其中,argv(在变量章节中有详细的介绍)是cshell预定义的参数数组,即command line传入的参数都存在放在argv数组中。

常见实例

假设我们现在需要编写一个run.csh脚本用于跑仿真,通过command line传递testcase,seed,cover和dump四个参数,分别用于指定测试名,种子,是否收集覆盖率,是否dump波形。当然还需要help选项,用于打印使用信息。因此我们的选项就可以总结为:

run -testcase TC_NAME -seed SEED -cover -dump
OR
run -help
#!/bin/csh # cshell声明行
# declare variable
set tc_name  = ""      # 默认名字为空
set tc_seed  = 1       # 默认种子为1
set tc_cover = "OFF"   # 默认关闭coverage选线
set tc_dump  = "OFF"   # 默认关闭dump波形功能

set args = ($argv[1-]) # 获取除了脚本名字之外的所有参数

# check that there are some arguments present
if($#args == 0 || $args[1] == "-help") then
  echo "usage: run [-testcase  TESECASE NAME] [-seed SEED] \
                   [-cover] [-dump] [-help]"
endif

# process each of the arguments to the script
while($#args > 0 )
  switch($args[1])
    case -testcase:
      shift args # remove "-testcase" element
      echo "testcase name is $args[1]"
      set tc_name = $args[1]
    breaksw
    case -seed:
      shift args # remove "-seed" element
      echo "the run time seed is: $args[1]"
      set tc_seed = $args[1]
    breaksw
    case -cover:
      shift args # remove "-cover" element
      echo "turn on cover option"
      set tc_cover = "ON"
    breaksw
    case -dump:
      shift args # remove "-dump" element
      echo "turn on cover option"
      set tc_dump = "ON"
    breaksw
    default:
      echo "option is error, please check, and try it again"
      exit
    breaksw
  endsw
  shift # remove non-option element 
end

# 下面就是针对具体仿真脚本的调用了,不是本文的重点 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值