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)
# -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.

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

 




Host by is-Programmer.com | Power by Chito 1.3.3 beta | © 2007 LinuxGem | Design by Matthew "Agent Spork" McGee