How many times executed bash command - bash

I have a script that will execute a PHP file multiple times:
#!/bin/bash
FILE=$(cat $1)
while IFS= read -r i in $file; do
php x.php "$i" &
done < "$1"
the text will be
a
b
c
d
What should i do to show me the line number that it's using out of how many, for example
3(current line)/200(number of total lines)
I did some research but i couldn't find anything.

You need to get the number of lines in the file first using wc, then add a counter to the loop. You're redirecting the file to the while loop, so you only need to assign the i variable while you read it:
#!/bin/bash
len=$(wc -l < "$1")
j=1
while read -r i; do
echo "$j / $len"
php x.php "$i" &
j=$(( j+1 ))
done < "$1"

Related

Current Count vs Total Count output in a single line using Bash

I need an output of current count vs total count in single line. I would like to know if this could be done Via Bash using 'for' 'while' loop.
Expecting an output that should only update the count and should not display multiple lines
File Content
$ cat ~/test.rtf
hostname1
hostname2
hostname3
hostname4
#!/bin/sh
j=1
k=$(cat ~/test.rtf | wc -l)
for i in $(cat ~/test.rtf);
do
echo "Working on line ($j/$k)"
echo "$i"
#or any other command for i
j=$((j+1))
done
EX:
Working on line (2/4)
Not like,
Working on line (2/4)
Working on line (3/4)
Assumptions:
OP wants to generate n lines of output that overwrite each other on successive passes through the loop
in OP's sample code there are two echo calls so in this case n=2
General approaches:
issue a clear at the beginning of each pass through the loop so as to clear the current output and reposition the cursor at the 'top' of the console/window
use tput to manage movement of the cursor (and clearing/overwriting of previous output)
Sample input:
$ cat test.rtf
this is line1
then line2
and line3
and last but not least line4
real last line5
clear approach:
j=1
k=$(wc -l < test.rtf)
while read -r line
do
clear
echo "Working on line ($j/$k)"
echo "${line}"
((j++))
done < test.rtf
tput approach:
j=1
k=$(wc -l < test.rtf)
EraseToEOL=$(tput el) # grab terminal specific code for clearing from cursor to EOL
clear # optional: start with a new screen otherwise use current position in console/window for next command ...
tput sc # grab current cursor position
while read -r line
do
tput rc # go (back) to cursor position stored via 'tput sc'
echo "Working on line ($j/$k)"
echo "${line}${EraseToEOL}" # ${EraseToEOL} forces clearing rest of line, effectively erasing a previous line that may have been longer then the current line of output
((j++))
done < test.rtf
Both of these generate the same result:
Something along these lines:
file=~/test.rtf
nl=$(wc -l "$file")
nl=${nl%%[[:blank:]]*}
i=0
while IFS= read -r line; do
i=$((i+1))
echo "Working on line ($i/$nl)"
done < "$file"
Your main question is how to avoid each the counter to be written to new lines. The newlines are \n characters, which is appended by echo. You want \r, like
for ((i=0; i<10; i++)); do
printf "Counter $i\r"
sleep 1
done
echo
When you echo something from the line you are working on, you will use \n again. I will use cut as an example of processing the inputline. Use the output string in the same printf command like
j=1
k=$(cat ~/test.rtf | wc -l)
while IFS= read -r line; do
printf "Working on line (%s): %s\r" "$j/$k" $(cut -c1-10 <<< "${line}")
sleep 1
((j++))
done < ~/test.rft
The problem with the above solution is that you will see output from previous lines when your last output is shorter than the previous one. When you know the maximum length that your processing of the line will show, you can force additional spaces:
j=1
k=$(cat ~/test.rtf | wc -l)
while IFS= read -r line; do
printf "Working on line (%5.5s): %-20s\r" "$j/$k" "$(cut -c1-20 <<< "${line}")";
sleep 1
((j++))
done < ~/test.rft

Replace text between 2 strings in bash with result of ls

I want to create an automation for a deployment, the js/css are generated with a prefix and I want to import them in the php file between the tags
Expected Output
...
/*bashStart*/
drupal_add_css(drupal_get_path("module","myModule")."/styles/c91c6d11.main.css");
drupal_add_js(drupal_get_path("module","myModule")."/scripts/j91c6d11.main.js");
/*bashEnd*/
...
I used awk and got me here so far but I have a problem, it's generating
...
/*bashStart*/
drupal_add_css(drupal_get_path("module","myModule")."/styles/c91c6d11.main.css
");
drupal_add_js(drupal_get_path("module","myModule")."/scripts/j91c6d11.main.js
");
/*bashEnd*/
...
here is the awk script:
awk 'BEGIN {p=1}/Start/{print;printf("drupal_add_css(drupal_get_path(\"module\",\"myModule\").\"/styles/");system("ls styles");printf("\");\n");printf("drupal_add_js(drupal_get_path(\"module\",\"myModule\").\"/scripts/");system("ls scripts");printf("\");\n");p=0}/Finish/{p=1} p' myModule.module > tmp;
Using ls within awk isn't very nice - I think you can do this entirely in the shell:
#!/bin/bash
p=1
while read -r line; do
[[ $line = '/*bashEnd*/' ]] && p=1
(( p )) && echo "$line"
if [[ $line = '/*bashStart*/' ]]; then
p=0
for style in styles/*; do
echo 'drupal_add_css(drupal_get_path("module","myModule")."/styles/'"$style"'");'
done
for script in scripts/*; do
echo 'drupal_add_js(drupal_get_path("module","myModule")."/scripts/'"$script"'");'
done
fi
done < file.php > output.php
Loop through the input file until you reach the "bashStart" line, then add the lines you want. Output goes to a file output.php which you can check before overwriting the original file. If you're feeling confident you can add && mv output.php file.php to the done line, to overwrite the original file.
The flag p controls which lines are printed. It is set to 0 when the "bashStart" line is reached and back to 1 when the bashEnd line is reached, so lines between the two are replaced.

printing line numbers that are multiple of 5

Hi I am trying to print/echo line numbers that are multiple of 5. I am doing this in shell script. I am getting errors and unable to proceed. below is the script
#!/bin/bash
x=0
y=$wc -l $1
while [ $x -le $y ]
do
sed -n `$x`p $1
x=$(( $x + 5 ))
done
When executing above script i get below errors
#./echo5.sh sample.h
./echo5.sh: line 3: -l: command not found
./echo5.sh: line 4: [: 0: unary operator expected
Please help me with this issue.
For efficiency, you don't want to be invoking sed multiple times on your file just to select a particular line. You want to read through the file once, filtering out the lines you don't want.
#!/bin/bash
i=0
while IFS= read -r line; do
(( ++i % 5 == 0 )) && echo "$line"
done < "$1"
Demo:
$ i=0; while read line; do (( ++i % 5 == 0 )) && echo "$line"; done < <(seq 42)
5
10
15
20
25
30
35
40
A funny pure Bash possibility:
#!/bin/bash
mapfile ary < "$1"
printf "%.0s%.0s%.0s%.0s%s" "${ary[#]}"
This slurps the file into an array ary, which each line of the file in a field of the array. Then printf takes care of printing one every 5 lines: %.0s takes a field, but does nothing, and %s prints the field. Since mapfile is used without the -t option, the newlines are included in the array. Of course this really slurps the file into memory, so it might not be good for huge files. For large files you can use a callback with mapfile:
#!/bin/bash
callback() {
printf '%s' "$2"
ary=()
}
mapfile -c 5 -C callback ary < "$1"
We're removing all the elements of the array during the callback, so that the array doesn't grow too large, and the printing is done on the fly, as the file is read.
Another funny possibility, in the spirit of glenn jackmann's solution, yet without a counter (and still pure Bash):
#!/bin/bash
while read && read && read && read && IFS= read -r line; do
printf '%s\n' "$line"
done < "$1"
Use sed.
sed -n '0~5p' $1
This prints every fifth line in the file starting from 0
Also
y=$wc -l $1
wont work
y=$(wc -l < $1)
You need to create a subshell as bash will see the spaces as the end of the assignment, also if you just want the number its best to redirect the file into wc.
Dont know what you were trying to do with this ?
x=$(( $x + 5 ))
Guessing you were trying to use let, so id suggest looking up the syntax for that command. It would look more like
(( x = x + 5 ))
Hope this helps
There are cleaner ways to do it, but what you're looking for is this.
#!/bin/bash
x=5
y=`wc -l $1`
y=`echo $y | cut -f1 -d\ `
while [ "$y" -gt "$x" ]
do
sed -n "${x}p" "$1"
x=$(( $x + 5 ))
done
Initialize x to 5, since there is no "line zero" in your file $1.
Also, wc -l $1 will display the number of line counts, followed by the name of the file. Use cut to strip the file name out and keep just the first word.
In conditionals, a value of zero can be interpreted as "true" in Bash.
You should not have space between your $x and your p in your sed command. You can put them right next to each other using curly braces.
You can do this quite succinctly using awk:
awk 'NR % 5 == 0' "$1"
NR is the record number (line number in this case). Whenever it is a multiple of 5, the expression is true, so the line is printed.
You might also like the even shorter but slightly less readable:
awk '!(NR%5)' "$1"
which does the same thing.

Hanging bash loop script?

varrr=0
while read line
do
if [ $line -gt 500 -a $line -le 600 ]; then # for lines 501-600
echo $line >> 'file_out_${varrr}.ubi'
fi
done << 'file_in_${varrr}.ubi'
file_in_${varrr}.ubi is a text file with around 1000 lines. I want to print lines 501-600 to new file.
Running this code leaves my Ubuntu terminal with a > symbol on a new line, as if I need to type another command to finish the loop. I can' figure out what is wrong with this loop though. Seems like it's complete. See any mistakes I've made? Thanks.
I'm only going to answer your specific question: it's because you used a heredoc << symbol, instead of a redirection <. Your last line should read:
done < 'file_in_${varrr}.ubi'
(observe the single <).
But then you'll realize that you have some quoting problems. So, your last line should read:
done < "file_in_${varrr}.ubi"
(observe the double quotes ").
Similarly, watch out your quotings in line 6. You should have this instead:
echo "$line" >> "file_out_${varrr}.ubi"
(double quotes " for file_out_${varrr}.ubi).
But then, this will not behave as you expect... Maybe this will do:
varrr=0
linenb=0
while IFS= read -r line; do
((++linenb))
if ((linenb>500 && linenb<=600)); then # for lines 501-600
echo "$line" >> "file_out_${varrr}.ubi"
fi
done < "file_in_${varrr}.ubi"
Hope this helps!
If you just want to print lines from 501 to 600, why don't you use the following?
awk 'NR>=501 && NR<=600' file_in > file_out
awk 'NR==n' myfile prints the line n of the file myfile. Then, you can use ranges as I writted above.
You can simply use sed. It's the simplest tool for it and is cleaner and faster than a while loop with tests.
varrr=0
sed -n 501,600p "file_in_${varrr}.ubi" >> "file_out_${varrr}.ubi"
Or
varrr=0
sed -n 501,600p "file_in_${varrr}.ubi" > "file_out_${varrr}.ubi"
If you want to override existing data.
The mistake in your loop by the way is because you're not using a counter and comparing your line number by the line itself instead.
varrr=0
counter=0
while read line; do
(( ++counter ))
[[ counter -gt 500 && counter -le 600 ]] && echo "$line"
done < "file_in_${varrr}.ubi" > "file_out_${varrr}.ubi"
Noticeably you need to use < for input not << and place your variables around double quotes not single quotes.

Creating a bash script that acts as a fortune teller or "magic 8 ball" essentially

I am trying to create a bash script that is essentially like a magic 8 ball with 6 different responses (Yes, No, Maybe, Hard to tell, Unlikely, and Unknown). The key is that once a response is given, it should not be given again until all responses have been given.
Here is what I have so far:
#!/bin/bash
echo "Ask and you shall receive your fortune: "
n=$((RANDOM*6/32767))
while [`grep $n temp | wc awk '{print$3}'` -eq 0]; do
n=$((RANDOM*6/32767))
done
grep -v $n temp > temp2
mv temp2 temp
Basically I have the 6 responses all on different lines in the temp file, and I am trying to construct the loops so that once a response is given, it creates a new file without that response (temp2), then copies it back to temp. Then once the temp file is empty it will continue from the beginning.
I'm quite positive that my current inner loop is wrong, and that I need an outer loop, but I'm fairly new to this and I am stuck.
Any help will be greatly appreciated.
Try something like this:
#!/bin/bash
shuffle() {
local i tmp size max rand
# $RANDOM % (i+1) is biased because of the limited range of $RANDOM
# Compensate by using a range which is a multiple of the array size.
size=${#array[*]}
max=$(( 32768 / size * size ))
for ((i=size-1; i>0; i--)); do
while (( (rand=$RANDOM) >= max )); do :; done
rand=$(( rand % (i+1) ))
tmp=${array[i]} array[i]=${array[rand]} array[rand]=$tmp
done
}
array=( 'Yes' 'No' 'Maybe' 'Hard to tell' 'Unknown' 'Unlikely' )
shuffle
for var in "${array[#]}"
do
echo -n "Ask a question: "
read q
echo "${var}"
done
I wrote a script that follows your initial approach (using temp files):
#!/bin/bash
# Make a copy of temp, so you don't have to recreate the file every time you run this script
TEMP_FILE=$(tempfile)
cp temp $TEMP_FILE
# You know this from start, the file contains 6 possible answers, if need to add more in future, change this for the line count of the file
TOTAL_LINES=6
echo "Ask and you shall receive your fortune: "
# Dummy reading of the char, adds a pause to the script and involves the user interaction
read
# Conversely to what you stated, you don't need an extra loop, with one is enough
# just change the condition to count the line number of the TEMP file
while [ $TOTAL_LINES -gt 0 ]; do
# You need to add 1 so the answer ranges from 1 to 6 instead of 0 to 5
N=$((RANDOM*$TOTAL_LINES/32767 + 1))
# This prints the answer (grab the first N lines with head then remove anything above the Nth line with tail)
head -n $N < $TEMP_FILE | tail -n 1
# Get a new file deleting the $N line and store it in a temp2 file
TEMP_FILE_2=$(tempfile)
head -n $(( $N - 1 )) < $TEMP_FILE > $TEMP_FILE_2
tail -n $(( $TOTAL_LINES - $N )) < $TEMP_FILE >> $TEMP_FILE_2
mv $TEMP_FILE_2 $TEMP_FILE
echo "Ask and you shall receive your fortune: "
read
# Get the total lines of TEMP (use cut to delete the file name from the wc output, you only need the number)
TOTAL_LINES=$(wc -l $TEMP_FILE | cut -d" " -f1)
done
$ man shuf
SHUF(1) User Commands
NAME
shuf - generate random permutations
SYNOPSIS
shuf [OPTION]... [FILE]
shuf -e [OPTION]... [ARG]...
shuf -i LO-HI [OPTION]...
DESCRIPTION
Write a random permutation of the input lines to standard output.
More stuff follows, you can read it on your own machine :)

Resources