How to create a stack in a shell script?

I need to create a stack in a shell script to pass values โ€‹โ€‹that will be processed in a loop. The first requirement is that this must be implemented in a portable way, since I want to use the script as a portable installer (at least between Unix-like operating systems). The second requirement is that it must be changed inside the loop, because new information may appear when the loop processes the record in a recursive manner. The third requirement is that I have more than one line of information for each record (this is basically a fixed number, and when it cannot be calculated based on the first line of information).

My attempt is to use the stack file:

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

This works fine, except that it feels wrong. Is there a less "hacky" way to do this?

Thanks in advance for your help!

+4
source share
5 answers

Check out the section here, Example 27-7. From empty arrays and empty elements. In particular, the comments say: โ€œAboveโ€ is โ€œpushโ€, and โ€œpopโ€:

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

If you want to encode multiple lines for each element, I suggest you base64 or JSON encode lines. You can also use url encoding or escape characters using echoes.

Since you require the use of arrays, you can use this example arrays in sh:

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

+2
source

Instead of using a file, it seems that it would be easier to use a directory and store each item in its own file. For instance:

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

Unfortunately, I do not think that a solution with a cat will work. It can work on Linux, but I use FreeBSD, and I tried to use cat to import tempfiles, and it failed all the time.

The problem with cat (at least with FreeBSD) is that it forces the shell to interpret its "output" as a literal command, and also disables certain characters, which also causes problems.

My possible solution was to convert the mentioned tempfiles into holders for variables and then import them using the source command. It works, but I don't like it; mainly because I have to do some ugly cutting with the specified to prefix the data with the name of the variable and enclose it in quotation marks.

So, in the data file you will have: -

 variable=foobar 

Then in the script, I would do what created my output for the variable, and then, to get it in the script, would use: -

 source datafile 

at what point could I use the variable.

However, although this does not look like a stack, it works to store data. I do not like using single variables in shell scripts if I can avoid them; mainly because again it means resorting to the ugly lookup hacking, and can be annoying for debugging.

0
source

It should be pretty cross-platform. It does not use arrays, so it can work on older versions of the 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 puts data in variables like $Item1 , $Item2 , $Item3 , etc. Use them as follows:

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

Pop itself destroys the variable $Item with the highest number after printing its contents. To save the contents in another variable, do this:

 Variable='Pop' 

Append adds a line to the variable at the top of the stack. For instance:

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

$Stack stores the height of the stack. Since there is no error handling in these functions, you will need to reset them if there is a stack overflow. Better yet, if you can just check it to prevent one - not Pop or Append , if $Stack not 1 or more.

0
source

Bashisms are disgusting, aren't they? If you need arrays in your program, you need to use ... assembler (joke)! Well, here is how I implemented the stack in 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 
-1
source

Source: https://habr.com/ru/post/1437716/


All Articles