我正在寻找一个用于简单 FIFO 队列的便携式 bash 解决方案。我的队列输入始终是一行文本。我想出了一个简单的解决方案,效果很好。
fifo_queue="fifo.txt";
fifo_tmp="fifo.tmp"
# put a new line into FIFO
echo "append to FIFO a line" >> $fifo_stack;
#get line back from FIFO
echo $(head -n1 "$fifo_queue")
tail -n +2 "$fifo_queue" > "$fifo_tmp" && mv "$fifo_tmp" "$fifo_queue"
现在的问题是:是否有更优雅(即易于理解)的方式来检索 FIFO 行。例如用简单的一行而不是两行?
如果要编辑文件,请使用编辑器;不要用
tail
拼凑一些东西。
put () {
printf '%s\n' "$1" >> "$2"
}
get () {
printf '1p\n1d\nwq\n' | ed -s "$1"
}
然后
$ put a queue.txt
$ put b queue.txt
$ get queue.txt
a
$ put c queue.txt
$ get queue.txt
b
检索下一个 FIFO 行的一个选项是使用 POSIX 标准 ed 实用程序:
printf '%s\n' 1p 1d w | ed -s -- "$fifo_stack"
这会调用 FIFO 文件上的
ed
并向其发出 3 个命令。 1p
打印第一行。 1d
删除第一行。 w
将更改保存回文件。
如果 FIFO 文件非常大,您可能会遇到这种方法的问题,但在现代机器上达到数十兆字节应该没问题。
对于 fifo 堆栈,逻辑的修改版本将类似于以下脚本。 当然,您需要修改它以允许将所需的行作为命令行上的参数(或作为重定向输入)传递。
作为从文件就地中删除堆栈/缓冲区文件中的第一行/最后一行,这将是sed的任务。
我能够使用非常简单的 sed 指令根据 fifo/lifo 模式识别删除第一行/最后一行的逻辑。 这是在下面的脚本中实现的。
#!/bin/bash
Push=0
Pop=0
fifo=1 ; mode="fifo" ### DEFAULT
lifo=0
while [ $# -gt 0 ]
do
case $1 in
--push ) Push=1 ; Pop=0 ; shift ;;
--pop ) Pop=1 ; Push=0 ; shift ;;
--fifo ) fifo=1 ; lifo=0 ; mode="fifo" ; shift ;; ### Buffer MODE
--lifo ) lifo=1 ; fifo=0 ; mode="lifo" ; shift ;; ### Stack MODE
* ) echo -e "\n Invalid parameter used on command line. Only valid options: [ --push | --pop ]\n Bye!\n" ; exit 1 ;;
esac
done
if [ ${Push} -eq 0 -a ${Pop} -eq 0 ]
then
echo -e "\n ERROR: must specify one of the two stack operations: --push or --pop \n Bye!\n" ; exit 1
fi
stack="${mode}.txt";
tmp="${mode}.tmp"
if [ ! -f "${stack}" ] ; then touch "${stack}" ; fi
i=$(( $(wc -l "${stack}" | awk '{ print $1 }' ) + 1 ))
if [ ${Push} -eq 1 ]
then
# put a new line into FIFO/LIFO
echo "append to FIFO - line ${i}" >> "${stack}"
wc -l "${stack}"
fi
if [ ${Pop} -eq 1 ]
then
if [ -s "${stack}" ]
then
#get line back from FIFO
if [ ${fifo} -eq 1 ]
then
head -n1 "${stack}"
sed --in-place '1d' "${stack}"
wc -l "${stack}"
else
tail -n1 "${stack}"
sed --in-place '$d' "${stack}"
wc -l "${stack}"
fi
else
echo -e "\t ERROR: attempt to pop line from EMPTY stack.\n" >&2
exit 1
fi
fi
会话日志如下:
me@OasisMega1:/0__WORK$ ./test_129.sh --push -fifo
1 fifo.txt
me@OasisMega1:/0__WORK$ ./test_129.sh --push -fifo
2 fifo.txt
me@OasisMega1:/0__WORK$ ./test_129.sh --push -fifo
3 fifo.txt
me@OasisMega1:/0__WORK$ ./test_129.sh --push -fifo
4 fifo.txt
me@OasisMega1:/0__WORK$ ./test_129.sh --pop -fifo
append to FIFO - line 1
3 fifo.txt
me@OasisMega1:/0__WORK$ ./test_129.sh --pop -fifo
append to FIFO - line 2
2 fifo.txt
me@OasisMega1:/0__WORK$ ./test_129.sh --pop -fifo
append to FIFO - line 3
1 fifo.txt
me@OasisMega1:/0__WORK$ ./test_129.sh --pop -fifo
append to FIFO - line 4
0 fifo.txt
me@OasisMega1:/0__WORK$ ./test_129.sh --pop -fifo
ERROR: attempt to pop line from EMPTY stack.
me@OasisMega1:/0__WORK$
LIFO 模式的会话日志如下:
me@OasisMega1:0__WORK$ ./test_129.sh --push --lifo
1 lifo.txt
me@OasisMega1:0__WORK$ ./test_129.sh --push --lifo
2 lifo.txt
me@OasisMega1:0__WORK$ ./test_129.sh --push --lifo
3 lifo.txt
me@OasisMega1:0__WORK$ ./test_129.sh --push --lifo
4 lifo.txt
me@OasisMega1:0__WORK$ ./test_129.sh --pop --lifo
append to FIFO - line 4
3 lifo.txt
me@OasisMega1:0__WORK$ ./test_129.sh --pop --lifo
append to FIFO - line 3
2 lifo.txt
me@OasisMega1:0__WORK$ ./test_129.sh --pop --lifo
append to FIFO - line 2
1 lifo.txt
me@OasisMega1:0__WORK$ ./test_129.sh --pop --lifo
append to FIFO - line 1
0 lifo.txt
me@OasisMega1:0__WORK$ ./test_129.sh --pop --lifo
ERROR: attempt to pop line from EMPTY stack.
me@OasisMega1:0__WORK$
此版本的脚本包含在命令行上提供缓冲区/堆栈的输入,以及显示当前缓冲区/堆栈内容的选项。 还提供返回代码,可用于决定需要“处理”的内容是否仍在缓冲区中。
#!/bin/bash
Push=0
Pop=0
Display=0
fifo=1 ; mode="FIFO" ### DEFAULT
lifo=0
dataline=""
while [ $# -gt 0 ]
do
case $1 in
--push ) Push=1 ; Pop=0 ; shift ;;
--pop ) Pop=1 ; Push=0 ; shift ;;
--show ) Push=0; Pop=0 ; Display=1 ; shift ;;
--fifo ) fifo=1 ; lifo=0 ; mode="FIFO" ; shift ;; ### Buffer MODE
--lifo ) lifo=1 ; fifo=0 ; mode="LIFO" ; shift ;; ### Stack MODE
-- ) shift ; dataline=${@} ; break ;;
* ) echo -e "\n Invalid parameter used on command line. Only valid options: [ --push | --pop ]\n Bye!\n" ; exit 1 ;;
esac
done
if [ ${Display} -eq 1 ]
then
if [ ${fifo} -eq 1 ]
then
if [ -f FIFO.txt ]
then
if [ -s FIFO.txt ]
then
wc -l FIFO.txt >&2
echo -e "Hit return to view contents ...\c" >&2 ; read k <&2
more FIFO.txt
RC=98
else
echo -e "\n FIFO buffer is empty at this time.\n" >&2
RC=0
fi
else
echo -e "\n FIFO buffer does not exist.\n" >&2
RC=99
fi
fi
if [ ${lifo} -eq 1 ]
then
if [ -f LIFO.txt ]
then
if [ -s LIFO.txt ]
then
wc -l LIFO.txt >&2
echo -e "Hit return to view contents ...\c" >&2 ; read k <&2
more LIFO.txt
RC=98
else
echo -e "\n LIFO buffer is empty at this time.\n" >&2
RC=0
fi
else
echo -e "\n LIFO buffer does not exist.\n" >&2
RC=99
fi
fi
exit ${RC}
fi
if [ ${Push} -eq 1 -a -z "${dataline}" ]
then
echo -e "\n ERROR: did not provide data for capture in BUFFER/STACK\n Bye!\n" ; exit 1
fi
if [ ${Push} -eq 0 -a ${Pop} -eq 0 ]
then
echo -e "\n ERROR: must specify one of the two stack operations: --push or --pop \n Bye!\n" >&2 ; exit 1
fi
stack="${mode}.txt";
tmp="${mode}.tmp"
if [ ! -f "${stack}" ] ; then touch "${stack}" ; fi
if [ ${Push} -eq 1 ]
then
# put a new line into FIFO/LIFO
echo "${dataline}" >> "${stack}"
wc -l "${stack}" >&2
fi
if [ ${Pop} -eq 1 ]
then
if [ -s "${stack}" ]
then
#get line back from FIFO
if [ ${fifo} -eq 1 ]
then
head -n1 "${stack}"
sed --in-place '1d' "${stack}"
wc -l "${stack}" >&2
else
tail -n1 "${stack}"
sed --in-place '$d' "${stack}"
wc -l "${stack}" >&2
fi
else
echo -e "\t ERROR: attempt to pop line from EMPTY stack.\n" >&2
exit 1
fi
fi
基于目录的版本,具有简单的用法、常见问题的错误处理和 O(1) 性能特征(意味着它不会随着队列变长而变慢):
$ source fifo-utils.bash # assuming this file has the below code
$ get fifo.d
ERROR: fifo directory 'fifo.d' not initialized
$ put fifo.d 13
$ get fifo.d
13
$ get fifo.d
ERROR: fifo directory 'fifo.d' is empty
$ put fifo.d fourteen
$ put fifo.d fifteen
$ get fifo.d
fourteen
$ get fifo.d
fifteen
$ get fifo.d
ERROR: fifo directory 'fifo.d' is empty
实现(一定要注意“未来增强”列表):
# Format:
# "head" contains the name of the next item ready for read
# "tail" contains the next place where newly-added data would be written
#
# If "head" matches "tail", the FIFO is empty.
#
# Possible future enhancements:
# - Use create-and-rename pattern to update counters atomically
# - If head or tail is unexpectedly empty, but numbered files exist, recover from
# highest/lowest numbered file
# - Lock head and tail files with flock to make concurrent use safe
# - Add "get" support for blocking if queue is empty (can do this by using inotifywait
# to block until tail is updated before proceeding should head == tail)
get() {
local fifo_dir=$1 head tail
[[ -s $fifo_dir/head && -e $fifo_dir/tail ]] || {
echo "ERROR: fifo directory ${fifo_dir@Q} not initialized" >&2
return 1
}
head=$(<"$fifo_dir/head") || return
tail=$(<"$fifo_dir/tail") || return
(( head >= tail )) && {
echo "ERROR: fifo directory ${fifo_dir@Q} is empty" >&2
return 2
}
[[ -e "$fifo_dir/$head" ]] || {
echo "ERROR: fifo directory ${fifo_dir@Q} has head at $head, but data does not exist" >&2
}
cat -- "$fifo_dir/$head" || return
printf '%s\n' "$(( head + 1 ))" >"$fifo_dir/head" || return
}
put() {
local fifo_dir=$1 data=$2 head tail
[[ -d $fifo_dir ]] || { mkdir -p -- "$fifo_dir" || exit; }
[[ -s "$fifo_dir/head" ]] || { echo 0 >"$fifo_dir/head" || return; }
[[ -s "$fifo_dir/tail" ]] || { echo 0 >"$fifo_dir/tail" || return; }
head=$(<"$fifo_dir/head") || return
tail=$(<"$fifo_dir/tail") || return
printf '%s\n' "$data" >"$fifo_dir/$tail" || return
echo "$(( tail + 1 ))" >"$fifo_dir/tail" || return
}
您可以使用真正的fifo。示例
#!/bin/bash
#--- setup
FIFO="fifo.tmp"
rm -f "$FIFO"
mkfifo "$FIFO"
#--- sending data in background
for (( i = 0; i < 10; i++ )); do
echo $i
sleep 1
done >"$FIFO" &
#--- receiving data
echo "start"
while read line; do
echo "> $line"
done < "$FIFO"
echo "stop"
#--- clean
rm -f "$FIFO"