yanbin's Blog
简单 Shell 编程 FAQ(〇)
0.sed 命令如何使用 shell 脚本某个变量的值?
使用 '“'括起表达式,或者即不用 '‘' 也不用 '“' 括起表达式。
'expression' 是使用 sed 时常用/常见的方式。
可以用 "expression" 的形式给 sed 指定表达式参数。
shell 对待参数的方式:
1)使用 '‘' 括起来的字符以它的文本形式对待。$ 就是 $ 这个字符,不会对待为展开变量的值;
2)使用 '“' 括起来的 '$ '用于展开变量的值,'\' 用来转义,其它字符仍然以文本形式对待;
3)执行命令时指定参数而不用'‘' 或'“' 括起来与使用 '“'类似;
# '$' 在这里用于匹配 '$'字符。 sed 's/A $foobar value/foobar/g' foobar.txt
# $ 展开 foobar 这个变量的值。转义的 \$ 匹配 '$'。 # 参数在传递给 sed 程序时已经完成变量值展开和转义了。 # 完成转义和变量展开的是 shell 而不是 sed. sed "s/A $foobar \$value/foobar/g" foobar.txt
# 这个与用 '“'括起来的效果是相同的。 sed s/A $foobar \$value/$/g foobar.txt
1.sed 如何从文件中直接删除一行或多行?
使用 sed 的 d 命令。
# 不熟悉 sed 时一般会写这样的代码。这种方式容易出错且耗费资源。 cat foobar.txt | sed 's/patter to match//gp' > tmp_file.txt mv tm_file.txt foobar.txt
# sed 的 d 命令 加 -i 参数 可以完成直接修改文件的操作。 sed -i '/pattern to match/d' foobar.txt
# 只输未匹配即没有被删除的行,而不修改文件 sed 'pattern to match/d' foobar.txt
-i[SUFFIX], --in-place[=SUFFIX]
edit files in place (makes backup if SUFFIX supplied)
edit files in place (makes backup if SUFFIX supplied)
# -i 参数接受一个可选的 suffix, 指定这个 suffix, sed 会修改文件前备份文件 sed -i.bak '/pattern to match/d' foobar.txt
2.找出只在一个文件中出现,不在另一个文件中出现的行?
使用 diff 命令,并且设置 --LTYPE-line-format="", 并且要求两个文件的行时排序过的。
# 输出在 file1 中出现,不在 file2 中出现的行。 diff --new-line-format="" --unchanged-line-format="" file1 file2
基本原理:
--new-line-format, --unchanged-line-format, --old-line-format 参数分别用来操作 diff 格式化输出:
(file2 中)新增加的行、没有改变过的行、以及(在 file2 中)删除的行。
参数的值是 "",也就是不输出。
(a)--new-line-format="", 不会输出只在 file2 中出现的行;
(b)--unchanged-line-format="", 不会输出在两个文件中都出现的行;
(c)指定了 --new-line-format="", --old-line-format 没有指定值,diff 默认直接输出只在 file1出现 old lines.
换个思路,假设一个 new_file, 一个 old_file, 都排序过:
# 输出在 new_file 中出现,不在 old file 中出现的行。 # 指定 --old-line-format=“”, 没有指定 --new-line-format,diff 默认直接输出 new line, 没有前导的 '<'. diff --old-line-format="" --unchanged-line-format="" old_file new_file
如果文件没有排序过也可以用 <(sort file1) 的方式排序
diff --new-line-foramt="" --unchanged-line-format=="" <(sort file1) <(sort file2)
3.sort 用指定 key (某些字符) 排序?uniq 按每行的前 N 个字符去重?
sort --key 参数; uniq -w 参数
# 以每行的 1到15个字符为 key 排序。1,15 都是 position, 第一个字符的 position 是 1. sort --key 1,15 foobar.txt # uniq 按每行的前 12 字符去重 sort --key 1,15 foobar.txt | uniq -w 12
sort:
-k, --key=KEYDEF sort via a key; KEYDEF gives location and type
KEYDEF is F[.C][OPTS][,F[.C][OPTS]] for start and stop position
uniq:
-w, --check-chars=N compare no more than N characters in lines
4.超时退出一个程序的执行?
timeout 程序:run a command with a time limit.
# 5 秒后向 command 程序发 SIGTERM 信号,退出 command 的执行。 tiemout 5 /path/to/slow/command with options
timeout 没有指定 --preserve-status 参数,返回 124.
timeout 有更灵活的用法。man timeout 可以获得更多参数介绍。
shell 脚本中,程序的标准输出重定向到 FIFO, 需要注意的问题
FIFO, 又称为命名管道 (named pipes)。与 PIPE 不同和相同之处:
1. PIPE 只能用于关联进程之间,比如: 父子进程之间,两个子进程之间。
2. 关于读写:
(a) 写一个没有读端的 PIPE,会触发 SIGPIPE(Broken pipe: write to pipe with no readers).
(b) 写一个没有读端的 FIFO,没有设置 O_NONBLOCKING 会阻塞,直到有进程读;
设置了 O_NONBLOCKING, 将会返回 -1.
(c) 没有写端时读 PIPE/FIFO 都是会阻塞,而设置了 O_NONBLOKING 读都会返回 -1.
由于 FIFO 没有读端则写端会阻塞的特性。
在 shell 脚步中,一个程序的标准输出重定向到 FIFO, 即写 FIFO 的程序启动之时或之后,
确保读 FIFO 的程序是否存在并且及时存在了,否则可能会有如下问题:
1. 读 FIFO 的程序没有启动过,写 FIFO 的程序永远阻塞在 写 FIFO 操作上。
2. 读 FIFO 的程序后来启动了,写 FIFO 的程序可能丢失了大量数据当其阻塞在写 FIFO 操作之上时。
3. 假如写 FIFO 的程序是一个多线程程序,她的产生数据操作与写数据操作是分开,并且有内部数据缓存,
当读 FIFO 的程序启动较晚时,会有大量数据要读,而且写 FIFO 程序也有大量数据要写,
这至少会耗费时间。
4. 实在要考虑写 FIFO 程序与读 FIFO 程序的启动先后顺序问题,要考虑时间窗口。
5. Linux Programmer's Manual, pipe(7) 给出的设计思想:
Applications should not rely on a particular capacity: an application should be designed so that
a reading process consumes data as soon as it is available, so that a writing process does not
remain blocked.
remain blocked.
shell脚本使用 timeout + wait 完成: 超时退出执行,等待执行完毕并处理执行结果
具体需求是:
1.从文件中读取 seq, 使用 pub 程序将 seq 推送给定阅读了 cmd topic 的 peer client.
client 将处理结果(message)推送到 cmdresp topic. 这个过程是经过 server 的,可以不考虑。
pub 成功后记录 seq.
2. sub 程序订阅 cmdresp topic, 简单处理接收到的消息,并且记录到文件。
3. 对比 pub 成功的 seq 记录与接收到的 message, 获得一个 pub/sub 成功率。
问题是:
1. pub 和 sub 分别使用两个不同的管道,pub cmd 成功后并不等待 cmdresp.
甚至可以说: pub 程序并不知道 cmdresp 的存在,甚至不知道 sub 的存在.
2. sub 是阻塞的:没有任何消息也不退出。
3. pub message 到 peer client 到 cmdresp 返回之间的时间是不确定的。
消息在两个管道都有可能出现延迟。
4. sub 程序本身没有超时退出选项。
伪代码是:
timeout time sub cmdresp_topic > cmdresp_record while seq in read seqs: seq = parse_seq(seq) message = create_message(seq) pub cmd_topic message record(message, msg_record) // do something, maybe sleep 1s wait sub parse(cmdresp_record, msg_record)
shell 代码是:
tmp_file="$(mktemp) rm -f $tmp_file mkfifo $tmp_file # terminate $MOSQUITTO while $TIMEOUT period timeout $TIMEOUT \ $MOSQUITTO_SUB -t $topic_resp --cafile $CA --cert $CERT --key $KEY > $tmp_file & cat $tmp_file | cut -d',' -f1 | cut -d':' -f2 | sed -n 's/\"//gp' >> $SUB_LOG & N=0 for line in $(cat $SNS_FILE) do sn="$(echo "$line" | cut -d'&' -f1)" seq="$($LOOKUP_TOPIC $sn)" name="$(printf "%s%.4d" $sn $seq)" topic="router/$name/cmd" N=$((N+1)) mid="$(date +%Y%m%d%H%M%S)-$N" message=makemsg $mid $MOSQUITTO_PUB -t $topic --cafile $CA --cert $CERT --key $KEY -m "$message" sw="$(echo "$line" | cut -d'&' -f2)" record="$(date +%Y%m%d:%H),$sn,$sw,1" [ $? -eq 0 ] && echo "$record" >> $PUB_LOG [ $((N % 10)) -eq 0 ] && sleep $SLEEP_TIME done wait # wait $MOSQUITTO_SUB and cat $tmpfile terminaation rm -f $tmp_file if [ -f $SUB_LOG -a -f $PUB_LOG ]; then tmp_file="$(mktemp -p $RECORD_DIR)" for line in $(cat $SUB_LOG) do sed "s/\(.*$line.*\),1/\1,0/g" $PUB_LOG > $tmp_file cp $tmp_file $PUB_LOG done rm -f $tmp_file fi