如何在shell脚本中创建堆栈?

时间:2012-10-04 02:02:56

标签: shell stack sh

我需要在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”的方式来做到这一点?

提前感谢您的帮助!

5 个答案:

答案 0 :(得分:2)

查看此处“实例27-7。空数组和空元素”部分。具体来说,评论说,上面是'推'而''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/

答案 1 :(得分:2)

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

#!/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

答案 2 :(得分:0)

不幸的是,我认为用cat的解决方案不会起作用。它可能适用于Linux,但我使用的是FreeBSD,我尝试使用cat来导入tempfiles的内容,并且它经常失败。

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

我最终的解决方案是将所述临时文件转换为变量的持有者,然后使用source命令导入它们。这有效,但我不喜欢它;主要是因为我必须做一些丑陋的切割,以便用变量名称作为数据前缀,并用引号括起来。

所以在数据文件中,你有: -

variable=foobar

然后在脚本中,我会做任何为变量创建输出的内容,然后将其放入脚本中将使用: -

source datafile

此时我可以使用变量。

尽管如此,这并不像堆栈那样,但它可以存储数据。我不喜欢在shell脚本中使用单独的变量,如果我也可以避免它们;主要是因为它再次意味着诉诸丑陋的替换hackery,并且可能会变得烦人的调试。

答案 3 :(得分:0)

这应该是跨平台的。它不使用数组,因此可以在较旧的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存储堆栈的高度。由于这些功能没有错误处理,因此如果有堆栈下溢,则需要将其重置。 更好的是,您可以检查一下以防止出现这种情况-除非Pop为1或更大,否则不要Append$Stack

答案 4 :(得分:-1)

Bashisms很恶心,不是吗?如果您的程序中需要数组,则需要使用... assembler (开玩笑)!好吧,这就是我在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