read textoutput and skip current loop - bash

I have a script with a loop over some directories and in each of them it executes a program.
folders=( "1" "2" )
for i in "${folders[#]}"
do
cd $i
output=$(program)
while read -r line; do
match "$line"
done <<< "$output"
some code here
cd ..
done
Now i want the script to stop the running program if $line matches with a given string and then start working on the next element of ${folders[#]}. Basically Strg+c from inside the script.
Edit: I cannot access the program and make it stop itself should the string appear.
Thanks

Now i want the script to stop the running program if $line matches
with a given string
if [ "$line" = "Put some similar text in here" ]
then
exit 0
fi
This will stop the programm, like you wanted.
then start working on the next element of ${folders[#]}
This is something different.
You can try to switch the code like this ...
folders=( "1" "2" )
for i in "${folders[#]}"
do
cd $i
output=$(program)
while read -r line; do
if [ "$line" = "Put some similar text in here" ]
then
break
fi
done <<< "$output"
# some commands ...
done
The if condition checks for similar text in a string and the break command will close the while loop.
Addition
The same code without using $output as temporary storage...
folders=( "1" "2" )
for i in "${folders[#]}"
do
cd $i
while read -r line; do
if [ "$line" = "Put some similar text in here" ]
then
break
fi
done <<< "$(program)"
# some commands ...
done
This way you will exit the extern programm in the loop.

Related

Bash script to download PDF using a CSV with name and url and auto-increment name

I'm trying to create a bash script that reads a CSV with two columns:
first column = name
second column = URL
and try to download a PDF file from the URL on the second column with a random name with letters and numbers .pdf and change the name using the first column.
The PDF name could be duplicate so if is duplicate I want to add numbers like:
Example %20 $5000.pdf
Example %20 $5000.1.pdf
Example %20 $5000.2.pdf
Because if I try to download wget and curl will not auto-increment with the output option.
I tried a lot of things but my limitations are taking too much time.
I created a counter that add the line number to the end, but if I got a larger PDF there will be unnecessary auto-increment numbers. (code below)
There should be a better method, but my lack of knowledge is taking too much time. So any help with that will be really appreciated, I'm a beginner on bash scripts.
Thanks for any help in advance!
CSV example:
Example %20 $5000,HTTP://example.com/djdiede.pdf
Example %20 $5000,HTTP://example.com/djdi42322ede.pdf
Example %30 $1000,HTTP://example.com/djd4234iede.pdf
Example %50 $1000,HTTP://example.com/dj43566diede.pdf
Code so far:
#!/bin/bash -e
COUNTER=1
while IFS=, read -r field1 field2
do
COUNTER=$[$COUNTER +1]
if [ "$field1" == "" ]
then
echo "Line $COUNTER field1 is empty or no value set"
elif [ "$field2" == "" ]
then
echo "Line $COUNTER field2 is empty or no value set"
else
pdf_file=$(echo $field1 | tr '/' ' ')
echo "================================================"
echo "Downloading $COUNTER $pdf_file..."
echo "================================================"
pdf_file_test="$pdf_file.pdf"
if [ -e "$pdf_file_test" ]; then
echo -e "\033[32m ^^^ File already exists!!! Adding line number at the end of the file: $pdf_file.$COUNTER.pdf \033[0m" >&2
wget -q -nc -O "$pdf_file."$COUNTER.pdf $field2
else
wget -q -nc -O "$pdf_file".pdf $field2
fi
fi
done < test.csv
This should help. I tried to stay close to your own coding style:
#!/bin/bash -e
LINECOUNTER=0
while IFS=, read -r field1 field2
do
LINECOUNTER=$[$LINECOUNTER +1]
if [ "$field1" == "" ]
then
echo "Line $LINECOUNTER: field1 is empty or no value set"
elif [ "$field2" == "" ]
then
echo "Line $LINECOUNTER: field2 is empty or no value set"
else
pdf_file=$(echo "$field1" | tr '/' ' ')
echo "================================================"
echo "Downloading $LINECOUNTER: $pdf_file..."
echo "================================================"
pdf_file_saveas="$pdf_file.pdf"
FILECOUNTER=0
while [ -e "$pdf_file_saveas" ]
do
FILECOUNTER=$[$FILECOUNTER +1]
pdf_file_saveas="$pdf_file.$FILECOUNTER.pdf"
done
if [ $FILECOUNTER -gt 0 ]
then
echo -e "\033[32m ^^^ File already exists!!! Adding number at the end of the file: $pdf_file_saveas \033[0m" >&2
fi
wget -q -nc -O "$pdf_file_saveas" "$field2"
fi
done < test.csv
Here's what I did:
use two counters: one for lines, one for files
when a file already exists, use file counter + loop to find the next 'empty slot' (i.e. file named <filename>.<counter-value>.pdf that does not exist)
fixed wrong line numbers (line counter needs to start at 0 instead of 1)
added double quotes where necessary/advisable
If you want to improve your script further, here are some suggestions:
instead of the big if ... elif ... else contruct, you can use if + continue, e.g. if [ "$field1" == "" ]; then continue; fi or even [ "$field1" == "" ] && continue
instead of terminating on error (#!/bin/bash -e), you could add error detection and handling after the wget call, e.g. if [ $? -ne 0 ]; then echo "failed to download ..."; fi

how to continue with the loop even though we use exit for a condition in shell script

I have a following list.txt file with the content
cat list.txt
one
two
zero
three
four
I have a shell script (check.sh) like below,
for i in $(cat list.txt)
do
if [ $i != zero ]; then
echo "the number is $i"
else
exit 1
fi
done
it gives output like below,
./check.sh
the number is one
the number is two
I want to have script which continue with the rest of the items in the list.txt, but it should not process zero and continue with the rest of item.
eg.
the number is one
the number is two
the number is three
the number is four
I tried using "return" but it did not work, gave error.
./check.sh: line 6: return: can only `return' from a function or sourced script
About exit (and return)
The command exit will quit running script. There is no way to continue.
As well, return command will quit function. There in no more way to continue.
About reading input file
For processing line based input file, you'd better to use while read instead of for i in $(cat...:
Simply try:
while read -r i;do
if [ "$i" != "zero" ] ;then
echo number $i
fi
done <list.txt
Alternatively, you could drop unwanted entries before loop:
while read -r i;do
echo number $i
done < <( grep -v ^zero$ <list.txt)
Note: In this specific case, ^zero$ don't need to be quoted. Consider quoting if your string do contain special characters or spaces.
If you have more than one entries to drop, you could use
while read -r i;do echo number $i ;done < <(grep -v '^\(zero\|null\)$' <list.txt)
Alternatively, once input file filtered, use xargs:
If your process is only one single command, you could avoid bash loop by using xargs:
xargs -n 1 echo number < <(grep -v '^\(zero\|null\)$' <list.txt)
How to use continue in bash script
Maybe you are thinking about something like:
while read -r i;do
if [ "$i" = "zero" ] ;then
continue
fi
echo number $i
done <list.txt
Argument of continue is a number representing number of loop to shortcut.
Try this:
for i in {1..5};do
for l in {a..d};do
if [ "$i" -eq 3 ] && [ "$l" = "b" ] ;then
continue 2
fi
echo $i.$l
done
done
(This print 3.a and stop 3 serie at 3.b, breaking 2 loop level)
Then compare with
for i in {1..5};do
for l in {a..d};do
if [ "$i" -eq 3 ] && [ "$l" = "b" ] ;then
continue 1
fi
echo $i.$l
done
done
(This print 3.a , 3.c and 3.d. Only 3.b are skipped, breaking only 1 loop level)

Is there a way to create an associative array from a text file in bash? [duplicate]

This question already has an answer here:
bash4 read file into associative array
(1 answer)
Closed 3 years ago.
I'm currently creating a list of commands so for example by saying "directory install plugin-name" I can install all needed plugins specified in an external list. This list is just a txt file with all plugin names. But I'm struggling getting all names in an associative array.
I've tried this one:
while IFS=";" read line;
do " communtyList[ $line ]=1 " ;
done < community-list.txt;
The desired output should be
communityList[test1]=1
communityList[test2]=1....
It need to be an associative array because I want to access it by words and not by index. This word will be implemented as parameters/arguments.
For example "install plugin" instead of "1 plugin"
So I can ask for example this way:
if [ ! -z "${!communtyList[$2]}" ];
Update, here the whole code:
#!/usr/bin/env bash
community(){
declare -A communtyList
while IFS= read line;
do communtyList[$line]=1 ;
done < community-list.txt;
# communtyList[test1]=1
# communtyList[test2]=1
# communtyList[test3]=1
# communtyList[test4]=1
if { [ $1 = 'install' ] || [ $1 = 'activate' ] || [ $1 = 'uninstall' ] || [ $1 = 'deactivate' ] ; } && [ ! -z $2 ] ; then
if [ $2 = 'all' ];
then echo "$1 all community plugins....";
while IFS= read -r line; do echo "$1 $line "; done < community-list.txt;
elif [ ! -z "${!communtyList[$2]}" ];
then echo "$1 community plugin '$2'....";
else
echo -e "\033[0;31m Something went wrong";
echo " Plugin '$2' does not exist.";
echo " Here a list of all available community plugins: ";
echo ${!communtyList[#]}
echo -e " \e[m"
fi
else
echo -e "\033[0;31m Something went wrong";
if [ -z $2 ];
then echo -e "[Plugin name] required. [community][action][plugin name] \e[m"
else
echo " Action '$1' does not exist.";
echo -e " Do you mean some of this? \n install \n activate \n uninstall \e[m"
fi
fi
echo ${!communtyList[#]}
}
"$#"
To use asociative array you have to declare it first
declare -A communityList
Then you can add values
communityList[test1]=1
communityList[test2]=2
...
Or with the declaration
declare -A communityList=(
communityList[test1]=1
communityList[test2]=2
...
)
The quotes around " communtyList[ $line ]=1 " mean you try to evaluate a command whose first character is a space. You want to take out those quotes, and probably put quotes around "$line" instead.
It's also unclear why you have IFS=";" -- you are not splitting the line into fields anyway, so this is not doing anything useful. Are there semicolons in your input file? Where and why; what do they mean?
You should probably prefer read -r unless you specifically require read to do odd things with backslashes in the input.
Finally, as suggested by Ivan, you have to declare the array's type as associative before you try to use it.
With those things out of the way, try
declare -A communityList
while read -r line; do
communtyList["$line"]=1
done < community-list.txt

Unexpected end of file bash script

This is just a simple problem but I don't understand why I got an error here. This is just a for loop inside an if statement.
This is my code:
#!/bin/bash
if (!( -f $argv[1])) then
echo "Argv must be text file";
else if ($#argv != 1) then
echo "Max argument is 1";
else if (-f $argv[1]) then
for i in `cut -d ',' -f2 $argv[1]`
do
ping -c 3 $i;
echo "finish pinging host $i"
done
fi
Error is in line 16, which is the line after fi, that is a blank line .....
Can someone please explain why i have this error ????
many, many errors.
If I try to stay close to your example code:
#!/bin/sh
if [ ! -f "${1}" ]
then
echo "Argv must be text file";
else if [ "${#}" -ne 1 ]
then
echo "Max argument is 1";
else if [ -f "${1}" ]
then
for i in $(cat "${1}" | cut -d',' -f2 )
do
ping -c 3 "${i}";
echo "finish pinging host ${i}"
done
fi
fi
fi
another way, exiting each time the condition is not met :
#!/bin/sh
[ "${#}" -ne 1 ] && { echo "There should be 1 (and only 1) argument" ; exit 1 ; }
[ ! -f "${1}" ] && { echo "Argv must be a file." ; exit 1 ; }
[ -f "${1}" ] && {
for i in $(cat "${1}" | cut -d',' -f2 )
do
ping -c 3 "${i}";
echo "finish pinging host ${i}"
done
}
#!/usr/local/bin/bash -x
if [ ! -f "${1}" ]
then
echo "Argument must be a text file."
else
while-loop-script "${1}"
fi
I have broken this up, because I personally consider it extremely bad form to nest one function inside another; or truthfully to even have more than one function in the same file. I don't care about file size, either; I've got several scripts which are 300-500 bytes long. I'm learning FORTH; fractalism in that sense is a virtue.
# while-loop-script
while read line
do
IFS="#"
ping -c 3 "${line}"
IFS=" "
done < "${1}"
Don't use cat in order to feed individual file lines to a script; it will always fail, and bash will try and execute the output as a literal command. I thought that sed printing would work, and it often does, but for some reason it very often substitutes spaces for newlines, which is extremely annoying as well.
The only absolutely bulletproof method of feeding a line to a script that I know of, which will preserve all space and formatting, is to use while-read loops, rather than substituted for cat or for sed loops, as mentioned.
Something else which you will need to do, in order to be sure about preserving whitespace, is to set the internal field seperator (IFS) to something that you know your file will not contain, and then resetting it back to whitespace at the end of the loop.
For every opening if, you must have a corresponding closing fi. This is also true for else if. Better use elif instead
if test ! -f "$1"; then
echo "Argv must be text file";
elif test $# != 1; then
echo "Max argument is 1";
elif test -f "$1"; then
for i in `cut -d ',' -f2 "$1"`
do
ping -c 3 $i;
echo "finish pinging host $i"
done
fi
There's also no argv variable. If you want to access the command line arguments, you must use $1, $2, ...
Next point is $#argv, this evaluates to $# (number of command line args) and argv. This looks a lot like perl.
Furthermore, testing is done with either test ... or [ ... ], not ( ... )
And finally, you should enclose at least your command line arguments in double quotes "$1". If you don't and there is no command line argument, you have for example
test ! -f
instead of
test ! -f ""
This lets the test fail and go on to the second if, instead of echoing the proper message.

Bash: Formatting results inside for loop from a ls command

How come the additional 'Line' insideecho "Line $line" is not prepended to all files inside the for loop?
#!/bin/bash
INPUT=targets.csv
IFS=","
[ ! -f $INPUT ] && { echo "$INPUT file not found"; exit 99; }
while read target user password path
do
result=$(sshpass -p "$password" ssh -n "$user"#"$target" ls "$path"*file* 2>/dev/null)
if [ $? -ne 0 ]
then
echo "No Heap dumps detected."
else
echo "Found a Heap dump! Possible OOM issue detected"
for line in $result
do
echo "Line $line"
done
fi
done < $INPUT
.csv file contents ..
rob#laptop:~/scripts$ cat targets.csv
server.com,root,passw0rd,/root/
script output ..
rob#laptop:~/scripts$ ./checkForHeapdump.sh
Found a Heap dump! Possible OOM issue detected
Line file1.txt
file2.txt
The statement:
for line in $result
performs word splitting on $result to get each element that $line should be set to. Word splitting uses the delimiters in $IFS. Earlier in the script you set this to just ,. So this loop will iterate over comma-separated data in $result. Since there aren't any commas in it, it's just a single element.
If you want to split it by lines, do:
IFS="
"
for line in $result

Resources