如何在shell脚本中创建一个栈?

8
我需要在shell脚本中创建一个栈,以便将值推送到循环中进行处理。第一个要求是必须以可移植的方式实现,因为我想将该脚本用作便携式安装程序(至少在类Unix操作系统之间)。第二个要求是它需要能够在循环内部被修改,因为循环处理条目时可能会出现新信息,以递归的方式。第三个要求是每个条目都有多行信息(这主要是固定数量的,当不是时,可以根据第一行信息进行计算)。
我的尝试是使用一个栈文件:
#!/bin/sh

echo "First entry" > stack.txt
echo "More info for the first entry" >> stack.txt
echo "Even more info for the first entry" >> stack.txt

while read ENTRY < stack.txt; do
    INFO2=`tail -n +2 stack.txt | head -n 1`
    INFO3=`tail -n +3 stack.txt | head -n 1`

    tail -n "+4" stack.txt > stack2.txt

    # Process the entry...

    # When we have to push something:
    echo "New entry" > stack.txt
    echo "Info 2" >> stack.txt
    echo "Info 3" >> stack.txt

    # Finally, rebuild stack
    cat stack2.txt >> stack.txt
done

这个方法完全可行,但感觉不太对。有没有更少“hacky”的方法呢?
非常感谢您的帮助!

4
若要打印文件的第二行,使用 sed -n 2p 要比将尾部输出再通过管道传递给头部更加简洁。 - William Pursell
5个回答

4

与其使用文件,使用目录并将每个项目存储在自己的文件中似乎更容易。例如:

#!/bin/sh 

count=0
push() { echo "$*" > $stackdir/item.$((++count)); }
pop() { 
    if test $count = 0; then
        : > $stackdir/data
    else
        mv $stackdir/item.$((count--)) $stackdir/data
    fi
}
trap 'rm -rf $stackdir' 0
stackdir=$( mktemp -d ${TMPDIR-/tmp}/stack.XXXX )

push some data
push 'another
data point, with
multiple lines'

pop
# Now $stackdir/data contains the popped data
cat $stackdir/data   # Print the most recently item pushed
push yet more data
pop
cat $stackdir/data   # Print 'yet more data'
pop
cat $stackdir/data

3
请查看这里的部分内容“Example 27-7. Of empty arrays and empty elements”。具体评论如下,上面是“push”,“pop”是:

http://tldp.org/LDP/abs/html/arrays.html

如果您想为每个元素编码多行,我建议您使用base64或JSON编码这些行。您还可以使用url编码或使用echo转义字符。

由于您需要使用数组,因此您可以使用sh中的数组示例:

http://www.linuxquestions.org/questions/linux-general-1/how-to-use-array-in-sh-shell-644142/


我不能使用数组,因为它是bash的特定功能 =( 我需要尽可能地使它具有可移植性。 - Janito Vaqueiro Ferreira Filho
1
@JanitoVaqueiroFerreiraFilho 如果是这种情况,请不要标记您的帖子为“bash”。zshkshtcsh 都有数组,但这不是 POSIX 的一部分。 - jordanm
1
使用这个在sh中的数组实现怎么样?我不知道它有多可移植... http://www.linuxquestions.org/questions/linux-general-1/how-to-use-array-in-sh-shell-644142/ - benathon
那是一个非常有趣的想法!=) - Janito Vaqueiro Ferreira Filho

3

这应该是相当跨平台的。它不使用数组,因此可以在较旧的shell版本上工作。

Push(){ let Stack++;eval "Item$Stack=\"$1\"";}
Pop(){ eval "echo -e \$Item$Stack;unset Item$Stack";let Stack--;}
Append(){ Push "`Pop`\n$1";}

Push命令将数据放入变量中,例如$Item1$Item2$Item3等。使用方法如下:

Push data; Push 'more data'; Push "remember to escape \"quotes\""

Pop命令会在打印出变量内容后销毁最高编号的$Item变量。如果需要将其内容保存到另一个变量中,可以这样做:

Variable=`Pop`

Append 将一行添加到堆栈顶部的变量中。例如:

Push "too much is always better than not enough"
Append "but it's almost as bad"

$Stack 存储了堆栈的高度。由于这些函数中没有错误处理,如果出现堆栈下溢,您需要重置它。更好的做法是,在不执行 PopAppend 操作之前,先检查 $Stack 是否大于等于1。


0

很遗憾,我认为使用cat的解决方案不会起作用。它可能在Linux中有效,但我正在使用FreeBSD,并尝试使用cat导入tempfiles的内容,但它一直失败。

使用cat的问题(至少在FreeBSD上)是它会导致shell将其输出解释为文字命令,并且还会遇到某些字符,这又会引起问题。

我的最终解决方案是将这些tempfiles转换为变量的容器,然后使用source命令导入它们。这个方法虽然可行,但我不喜欢它;主要是因为我必须使用said进行一些丑陋的切割,以便在数据前缀中添加变量名,并将其用引号括起来。

因此,在数据文件中,您将有:

variable=foobar

然后在脚本中,我会执行创建变量输出的任何操作,然后使用以下方式将其传递到脚本中:

source datafile

在那个时候,我可以使用这个变量。

尽管它看起来并不像一个堆栈,但它仍然可以用来存储数据。如果可能的话,我也不喜欢在shell脚本中使用单独的变量;主要是因为这意味着要求助于丑陋的替换技巧,而且调试起来可能会很麻烦。


-1

Bashisms很讨厌,不是吗?如果你的程序需要使用数组,那么你需要使用...汇编语言(开玩笑)!好吧,这就是我在POSIX Shell中实现栈的方式:

#!/bin/sh

# --------------------
# Stack implementation
# --------------------

s=""
stk=""
STACK_MAX_SIZE="65536"

# Delete all values from the stack:
stack_clear () {
    s=""
    stk=""
}

# To push a value into the stack:
stack_push () {
    local counter
    local cnt
    counter=$(echo -n "${s}" | wc --bytes)
    cnt=$(echo -n "${counter}" | wc --bytes)
    # ----- Internal check begin -----
    check=$(echo -n "${cnt}" | wc --bytes)
    if test "${check}" != "1"
    then
        echo "Internal error: it is real to meet such a long string..."
        exit 2s
    fi
    # ----- Internal check end -----
    stk=$(echo -n "${stk}${s}${counter}${cnt}")
    local check
    check=$(echo -n "${stk}" | wc --bytes)
    if test "${check}" -gt "${STACK_MAX_SIZE}"
    then
        echo "Error: stack overflow."
        exit 1
    fi
}

# To pull a value from the stack:
stack_pop () {
    local counter
    local cnt
    if test "${stk}" = ""
    then
        echo "Error: trying to pop from an empty stack."
        exit 1
    fi
    cnt=$(echo -n "${stk}" | tail --bytes=1)
    stk=$(echo -n "${stk}" | head --bytes=-1)
    counter=$(echo -n "${stk}" | tail --bytes=${cnt})
    stk=$(echo -n "${stk}" | head --bytes=-${cnt})
    s=$(echo -n "${stk}" | tail --bytes=${counter})
    stk=$(echo -n "${stk}" | head --bytes=-${counter})
    # ----- Internal check begin -----
    local check
    check=$(echo -n "${s}" | wc --bytes)
    if test "${check}" != "${counter}"
    then
        echo "Internal error: the stack is damaged."
        exit 2
    fi
    # ----- Internal check end -----
}

# ---------------
# The entry point
# ---------------

# Push "one", "two", "three" into the stack:
s="one"; stack_push
s="two"; stack_push
s="three"; stack_push

# Extract all the data from the stack:
while test "${stk}" != ""
do
    stack_pop
    echo "${s}"
done

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接