Progress Bar While Looping Script - bash

I would like to show a progress bar while running the following code. I'm calling a script within this code that could take a while depending on the variables passed in. I've tried some of the implementations from How to add a progress bar to a shell script? but have not been able to get them to work. In theory it should continue based off of whether or not the process is still running. If it's still running then show a progress bar.
cat $BLKUPLD | tr -d '\r' | while read line;
do
device_name=`echo $line | cut -d "," -f 1`
quantity_num=`echo $line | cut -d "," -f 2`
bash $SCRIPT $device_name $quantity_num
done
Please let me know if you need additional information.

Below is a progress bar script that will run until the bar is filled.
You will want to change the condition of the outermost while loop to instead check whether your $BLKUPLD has completed, and move the rest of your code inside the while loop where indicated (essentially, you may need to change MAX_ITERATIONS to a boolean condition, and play with where to insert the components of your code within this scaffold).
Ideally you would know how much remaining data you had, and could dynamically set MAX_ITERATIONS accordingly as you enter the loop logic; however, you mentioned that you were okay with having an infinitely looping progress bar as well, which might be the way you have to go if you aren't able to pre-define the end point of your script.
The main premise behind this script that differs from the other thread I linked, is that there are no hardcoded progress points: e.g. "[###__________]33%". Instead, the nested while loops serve to dynamically set the number of hashtags, and also dynamically pad the spacing following the hashtags to maintain a consistent span of progress.
#!/bin/sh
MAX_ITERATIONS=10
WHILE_ITERATOR=0
while [ $WHILE_ITERATOR -le $MAX_ITERATIONS ]
# __Add call to process checking script here.__
do
# Appending hashtags to progress bar string.
PROGRESS_BAR="["
HASHTAGS=$WHILE_ITERATOR
HASHTAGS_ITERATOR=0
while [ $HASHTAGS_ITERATOR -le $HASHTAGS ]
do
# Accounting for first pass through outer loop.
if [ $WHILE_ITERATOR -eq 0 ]; then
PROGRESS_BAR+=" #"
else
PROGRESS_BAR+="#"
fi
HASHTAGS_ITERATOR=$((HASHTAGS_ITERATOR+1))
done
# Appending trailing spaces to progress bar string.
SPACES=$((MAX_ITERATIONS-WHILE_ITERATOR-1))
SPACES_ITERATOR=0
while [ $SPACES_ITERATOR -le $SPACES ]
do
PROGRESS_BAR+=" "
SPACES_ITERATOR=$((SPACES_ITERATOR+1))
done
# Closing progress bar screen and adding return esc char.
PROGRESS_BAR+="]\r"
# Setting echo -n to run properly on Unix & Mac
if [ "`echo -n`" = "-n" ]; then
n=""
c="\c"
else
n="-n"
c=""
fi
# Print the progress bar without \n; reprints in place.
echo $n "$PROGRESS_BAR" $c
sleep 1
WHILE_ITERATOR=$((WHILE_ITERATOR+1))
done
# Print final iteration of progress bar string.
echo "$PROGRESS_BAR"

Related

Send zsh prompt to program and replace content with result

I would like to send my prompt contents to my snippets application and replace the line with the result:
Example initial prompt:
$ while foo bar
Example replaced prompt:
$ while foo ; do bar ; done
Having the first prompt I would run a shortcut and replace the line with the value returned by the program.
I imagine the solution would be something like this:
bindkey "^y" evaluateSnippets
evaluateSnippets() {
return mySnippetsTool <<< "$promptLine"
}
How can I accomplish that in zsh?
A further correlated question is if it is possible to replace just the selected part of the prompt in another shortcut.
evaluate-snippets () {
BUFFER=$(mySnippetsTool <<< $BUFFER)
}
zle -N evaluate-snippets
bindkey '^Y' evaluate-snippets
Within widgets (aka the functions behind the key bindings) the content of the edit buffer is contained in the parameter BUFFER. The edit buffer can also be modified by writing to BUFFER. So saving the output of mySnippetsTool in BUFFER should be enough. The command zle -N foo creates a widget named foo, which runs the function fo the same name when called.
As you can manipulate the contents of BUFFER in any way you want, it is also possible to modify only parts of it. The main caveat here is that the selection has to be done from withing the shell - e.g. visual-mode (v) with the vicmd keybindings or set-mark-command (Control+#) with the emacs keybindings - and (probably) cannot be done with the mouse. For example:
evaluate-snippets-selection () {
if [[ $CURSOR -gt $MARK ]]; then
start=$MARK
end=$(( CURSOR + 1 ))
else
start=$(( CURSOR + 1 ))
end=$MARK
fi
BUFFER="$BUFFER[0,start]$(mySnippetsTool <<< $BUFFER[start+1,end])$BUFFER[end+1,-1]"
}
zle -N evaluate-snippets-selection
bindkey '^Z' evaluate-snippets-selection
(Note: some fine-tuning for the indices and ranges might be necessary in order to match the expectation of what is currently selected. For example whether the current cursor position is part of the selection or not.)
You might not even need separate commands. As long as you did not set a mark and the cursor is at the very end of the line, both commands should deliver the same results.

Improving knowledge in Bash

This is more directed to learning about BASH rather than creating a specific code.
---Problem: Write a Bash script that takes a list of login names on your computer system as command line arguments, and displays these login names, full names and user-ids of the users who own these logins (as contained in the /etc/passwd file), one per line. If a login name is invalid (not found in the /etc/passwd file), display the login name and an appropriate error message. ---
If I needed to create a code to fulfill this problem could I do it using a "choice" list like this:
read -p "Enter choice: " ch
if [ "$ch" = "1" ]; then
function_1
else
if [ "$ch" = "2" ]; then
function_2
else
if [ "$ch" = "3" ]; then
function_3
else
if [ "$ch" = "4" ]; then
function_4
else
if [ "$ch" = "5" ]; then
function_5
fi x5
or would it have to be completed using a grep and test method where by the user read input must be taken into variables Name1 Name2.... NameN and are tested to the ect/passwd file via grep command.
#!/bin/bash
# pgroup -- first version
# Fetch the GID from /etc/group
gid=$(grep "$̂1:" /etc/group | cut -d: -f3)
# Search users with that GID in /etc/passwd
grep "^[^:]*:[^:]*:[^:]*:$gid:" /etc/passwd | cut -d: -f1`enter code here`
Please explain the best you can with some generic code. I am still learning the basics and testing different ideas. I apologize if these are very vague concepts, I am still getting the hang of BASH.
You would:
Accept (read) from the user the username,
Check if the username exists by retrieving the record using grep,
If positive result (i.e. you got data), display it as needed,
Otherwise, display an error message (or whatever you need).
You are almost there (got how to get input, how to search using grep). What you need is to get the result of the grep into a variable that you can check. You may try something like:
Result=$(grep....)
and then check the contents of Result.
To me it looks like you're missing an important part of the problem: the names are passed as arguments to the script:
./script.sh name1 name2 name3
If this is the case, then a loop would be the most appropriate thing to use:
for login; do
printf '%s: %s\n' "$login" "$(grep "^$login" /etc/passwd)"
done
Note that a for loop iterates over the list of arguments passed to the script $# by default.

How can one count the number of characters used in a command just executed in Bash?

Let's say I want to have a line print in such a way that it fills the space remaining in a terminal after an executed command. It could look something like this:
user1#host1:~>ls----------------------------------------------------------------
I have some functions that allow a line to be printed across a terminal. These ultimately use information about the terminal dimensions in order to do this. The functions are as follows:
returnTerminalDimension(){
dimension="${1}"
if [ "${dimension}" = "size" ]; then
stty size
else
if [ "${dimension}" = "height" ]; then
stty size | cut -d" " -f1
else
if [ "${dimension}" = "width" ]; then
stty size | cut -d" " -f2
fi
fi
fi
}
printCharacter(){
character="${1}"
numberOfTimesToPrintCharacter="${2}"
for (( currentPrintNumber = 0; currentPrintNumber<=${numberOfTimesToPrintCharacter}; currentPrintNumber++ ))
do
echo -n "${character}"
done
}
printLine(){
numberOfLines=1
terminalWidth="$(returnTerminalDimension "width")"
numberOfCharactersToPrint=$(echo "${numberOfLines}*(${terminalWidth})-1" | bc)
printCharacter "_" "${numberOfCharactersToPrint}"
}
There is a way to have a command executed right before a specified command is executed. This command (here, date is used as an example) can be specified as follows:
trap 'date' DEBUG
So, this would run right before the execution a specified command at each use of the carriage return.
How could I count the number of characters used in a command that has just been executed in order to combine all of these tricks to allow a line to fill the remaining after the command?

stopping 'sed' after match found on a line; don't let sed keep checking all lines to EOF

I have a text file in which each a first block of text on each line is separated by a tab from a second block of text like so:
VERBS, AUXILIARY. "Be," subjunctive and quasi-subjunctive Be, Beest, &c., was used in A.-S. (beon) generally in a future sense.
In case it is hard to tell, tab is long space between "quasi-subjunctive" and "Be".
So I am thinking off the top of my head a 'for' loop in which a var is set using 'sed' to read the first block of text of a line, upto and including the tab (or not, doesn't really matter) and then the 'var' is used to find subsequent matches adding a "(x)" right before the tab to make the line unique. The 'x' of course would be a running counter numbering the first instance '1' incrementing and then each subsequent match one number higher.
One problem I see is stopping 'sed' after each subsequent match so the counter can be incremented. Is there a way to do this, since it is "sed's" normal behaviour to continue on thru without stop (as far as I know) until all lines are processed.
You can set the IFS to TAB character and read the line into variables. Something like:
$ while IFS=$'\t' read block1 block2;do
echo "block1 is $block1"
echo "block2 is $block2"
done < file
block1 is VERBS, AUXILIARY. "Be," subjunctive and quasi-subjunctive
block2 is Be, Beest, &c., was used in A.-S. (beon) generally in a future sense.
Ok so I got the job done with this little (or perhaps big if too much overkill?) script I whipped up:
#!/bin/bash
sedLnCnt=1
while [[ "$sedLnCnt" -lt 521 ]] ; do
lN=$(sed -n "${sedLnCnt} p" sGNoSecNums.html|sed -r 's/^([^\t]*\t).*$/\1/') #; echo "\$lN: $lN"
lnNum=($(grep -n "$lN" sGNoSecNums.html|sed -r 's/^([0-9]+):.*$/\1/')) #; echo "num of matches: ${#lnNum[#]}"
if [[ "${#lnNum[#]}" -gt 1 ]] ; then #'if'
lCnt="${#lnNum[#]}"
((eleN = $lCnt-1)) #; echo "\$eleN: ${eleN}" # var $eleN needs to be 1 less than total line count of zero-based array
while [[ "$lCnt" -gt 0 ]] ; do
sed -ri "${lnNum[$eleN]}s/^([^\t]*)\t/\1 \(${lCnt}\)\t/" sGNoSecNums.html
((lCnt--))
((eleN--))
done
fi
((sedLnCnt++))
done
Grep was the perfect way to find line numbers of matches, jamming them into an array and then editing each line appending the unique identifier.

How to create a stack in a shell script?

I need to create a stack in a shell script in order to push values to be processed in a loop. The first requirement is that this must be implemented in a portable manner, as I want to use the script as a portable installer (at least between Unix-like operating systems). The second requirement is that it needs to be able to be altered inside the loop, because new information can appear while the loop is processing an entry, in a recursive manner. The third requirement is that I have more than one line of information per entry (this is mostly a fixed number, and when it isn't it can be calculated based on the first line of information).
My attempt is to use a 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 perfectly, except that it feels wrong. Is there a less "hacky" way to do this?
Thanks in advance for any help!
Rather than using a file, it seems like it would be easier to use a directory and store each item in its own file. For example:
#!/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
Checkout the section here "Example 27-7. Of empty arrays and empty elements". Specifically the comments say, Above is the 'push' and The 'pop' is:
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 the lines. You could also use url encoding or escape the characters using echo.
Since you require the usage of arrays, you may be able to use this example of arrays in sh:
http://www.linuxquestions.org/questions/linux-general-1/how-to-use-array-in-sh-shell-644142/
This should be pretty cross-platform. It doesn't use arrays, so it can work on older shell versions.
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 such as $Item1, $Item2, $Item3, etc. Use it like this:
Push data; Push 'more data'; Push "remember to escape \"quotes\""
Pop by itself destroys the highest-numbered $Item variable after printing its contents. To store the contents in another variable, do this:
Variable=`Pop`
Append adds a line to the variable on the top of the stack. For example:
Push "too much is always better than not enough"
Append "but it's almost as bad"
$Stack stores the height of the stack. Since there's no error handling in these functions, you'll need to reset it if there's a stack underflow.
Even better, you can just check it to prevent one - don't Pop or Append unless $Stack is 1 or more.
Unfortunately I don't think that solution with cat would work. It might work in Linux, but I am using FreeBSD, and I tried to use cat to import the contents of tempfiles, and it failed constantly.
The problem with cat (at least with FreeBSD) is that it causes the shell to interpret its' output as a literal command, and also trips over certain characters, which again causes problems.
My eventual solution was to convert said tempfiles to holders for variables, and then import them with the source command. This works, but I don't like it; mainly because I have to do some ugly cutting with said in order to prefix the data with the variable name, and encase it in quotes.
So in the data file, you'd have:-
variable=foobar
Then in the script, I'd do whatever created my output for the variable, and then to get it into the script would use:-
source datafile
at which point I could use the variable.
Still, even though this doesn't really resemble a stack, it works to store data. I do not like using lone variables in shell scripts if I can avoid them either; mainly because again, it means resorting to ugly substitution hackery, and can become annoying to debug.
Bashisms are disgusting, aren't they? If you need arrays in your program, then you need to use... assembler (kidding)! Well, this 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

Resources