bash:一个简单的 FIFO(先进先出)队列

问题描述 投票:0回答:5

我正在寻找一个用于简单 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 行。例如用简单的一行而不是两行?

bash fifo
5个回答
3
投票

如果要编辑文件,请使用编辑器;不要用

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

3
投票

检索下一个 FIFO 行的一个选项是使用 POSIX 标准 ed 实用程序:

printf '%s\n' 1p 1d w | ed -s -- "$fifo_stack"

这会调用 FIFO 文件上的

ed
并向其发出 3 个命令。
1p
打印第一行。
1d
删除第一行。
w
将更改保存回文件。

如果 FIFO 文件非常大,您可能会遇到这种方法的问题,但在现代机器上达到数十兆字节应该没问题。


2
投票

对于 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

2
投票

基于目录的版本,具有简单的用法、常见问题的错误处理和 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
}
  

2
投票

您可以使用真正的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"
© www.soinside.com 2019 - 2024. All rights reserved.