Assign specific content of a .txt file into a bash variable - bash

I have a txt file and i want to assign a specific word from inside the text file and its many lines. e.g Let's say that the text file is this sentence and the value 0.78 is the one that i want to get assigned. How do i do that?

I'm guessing you are looking for a number, so I'll extract anything that looks like a number:
grep -Ewo "[+-]?[0-9]*\.?[0-9]+" file
echo $n
0.78
The regex says it can have an optional +/- at the start, some optional digits, an optional decimal point and some more digits.
If you only want the number with a decimal point in it, and you want that in a bash variable, do this
var=$(grep -Ewo "[+-]?[0-9]*\.[0-9]*" file)
echo $var

I have no idea what your context is, but let's assume you want to match the number immediately following the word "value" in your text file, and you want to do it in pure Bash:
$ [[ $(<yourfile.txt) =~ value\ ([0-9.]*) ]] && echo ${BASH_REMATCH[1]}
0.78
See Conditional Constructs in the Bash manual for more information.

Related

Need to move a file having double digit numeric values in bash script

I want to move few files that have numeric values as part of filename. For example pattern 'ABC123_10_abc.txt' to a destination location as 'ABC123_abc.txt '
current command used:
mv $prefix_[0-9]_$suffix $prefix_$suffix;
But the above is not working for double digits. whereas if i put *[0-9] it is creating issue in identifying unique filename, so please suggest
Your question is tagged as bash, so I assume scripting is ok. This should help:
#!/usr/bin/env bash
for file; do
if [[ "${file}" =~ ^(.+)_[0-9]+_(.+)$ ]]; then
prefix="${BASH_REMATCH[1]}"
suffix="${BASH_REMATCH[2]}"
mv -v "${file}" "${prefix}_${suffix}"
else
echo "File '${file}' does not match regex, skipping"
fi
done
Save this script as e.g. renamer.sh, make it executable and run it with the files to rename provided as arguments, e.g. ./renamer.sh *.txt.
Each filename is matched against regular expression ^(.+)_[0-9]+_(.+)$ which dissects the filename into prefix and suffix per your description. The file is then renamed to ${prefix}_${suffix}. Works for any number of digits regarding the _xx_ part.
Note that this is based on the little information you provided. If that's not what you're looking for, please clarify your question by adding further details.

How to compare number with filenames in BASH

I have number of multiple files in a folder and their filenames contains alphanumeric values. For e.g. 045_gfds.sql, 46kkkk.sql, 47asdf.sql etc. I want to compare numbers in these filenames with another number stored in variable lets say $× =45 and find out files which has greater than number contain in filename. I am using Cygwin and currently only able to retrieve numbers using egrep command. for e.g.
filename="C:\scripts"
dir $filename | egrep -o [0-9]+
Output is : 045 46 47
I want output as filename after comparing greater than $=45 with all the filenames as:
46kkkk.sql
47asdf.sql
Need help with regular expressions for comparing greater than values in filename.
#!/bin/bash
dir="$1"
print_if_greater="45"
for fname in "$dir"/[0-9]*; do
num="${fname##*/}" # isolate filename from path
num="${num%%[^0-9]*}" # extract leading digits from filename
if (( num > print_if_greater )); then
printf '%s\n' "$fname"
fi
done
The above script will go through all file in the given directory that starts with at least one digit.
The filename is stripped from the path, and the initial digits in the filename are extracted using the variable expansion syntax of bash.
If the number that is extracted is greater than $print_if_greater, then the full pathname is displayed on standard output.
This script is invoked with the directory that you'd like to examine:
$ ./thescript.sh 'C:\scripts'
or
$ bash ./thescript.sh 'C:\scripts'
I haven't got access to Cygwin, so I haven't been able to test it with Window-styled paths. If the above doesn't work, try with C:/scripts as the path.
You can try this :
DIR="C:\scripts"
MAX=45
for FILE in "$DIR"/*
do
if
[[ "$FILE" =~ ^([0-9]+) ]]
then
NUMBER="${BASH_REMATCH[1]}"
if
[ "$NUMBER" -gt "$MAX" ]
then
echo "$FILE"
fi
fi
done
Please note I have not tested this code. It is bash-specific, and assumes the numbers are always at the beginning of the filename.

Bash: Using Number of Lines in File in Variable

I am trying to extract the number of lines from a file, and then use it in a variable. However, it keeps passing the file name, not the number of lines. I read through this question, but the examples are not working.
for i in $BASE/$TEMPLATE_DIR-$i$PRUNING_METHOD/*.txt
do
NUM_LINES=$(wc -l < $i)
echo $NUM_LINES
UPLOAD_CMD="sh sshpass_setup_verification.sh $EXP_ID-$i$PRUNING_METHOD__$NUM_LINES__features";
echo "$UPLOAD_CMD"
break 1;
done
Prints:
15 #the correct number of lines
sh sshpass_setup_verification.sh di-60sec-max/TemplateUser1.txt #where TemplateUser1.txt is the name of the file
Should print:
15
sh sshpass_setup_verification.sh di-60sec-max__15__features
A summary of what people are telling you in the comments:
for i in "${BASE}/${TEMPLATE_DIR}-${i}${PRUNING_METHOD}"/*.txt
do
num_lines=$(wc -l < "$i")
echo "$num_lines"
upload_cmd="sh sshpass_setup_verification.sh ${EXP_ID}-${i}${PRUNING_METHOD}__${num_lines}__features"
echo "$upload_cmd"
break
done
The key thing here is using double quotes around your parameter expansions and curly braces to disambiguate in situations where characters such as _ could be interpreted as part of a variable name but shouldn't. I've added them in several places where they aren't strictly needed but they do no harm.
I've also changed your variable names to lowercase. This will help you one day when you decide to have a variable called PATH and suddenly all your commands stop working.

Match exact word in bash script, extract number from string

I'm trying to create a very simple bash script that will open new link base on the input command
Use case #1
$ ./myscript longname55445
It should take the number 55445 and then assign that to a variable which will later be use to open new link based on the given number.
Use case #2
$ ./myscript l55445
It should do the exact same thing as above by taking the number and then open the same link.
Use case #3
$ ./myscript 55445
If no prefix given then we just simply open that same link as a fallback.
So far this is what I have
#!/bin/sh
BASE_URL=http://api.domain.com
input=$1
command=${input:0:1}
if [ "$command" == "longname" ]; then
number=${input:1:${#input}}
url="$BASE_URL?id="$number
open $url
elseif [ "$command" == "l" ]; then
number=${input:1:${#input}}
url="$BASE_URL?id="$number
open $url
else
number=${input:1:${#input}}
url="$BASE_URL?id="$number
open $url
fi
But this will always fallback to the elseif there.
I'm using zsh at the moment.
input=$1
command=${input:0:1}
sets command to the first character of the first argument. It's not possible for a one character string to be equal to an eight-character string ("longname"), so the if condition must always fail.
Furthermore, both your elseif and your else clauses set
number=${input:1:${#input}}
Which you could have written more simply as
number=${input:1}
But in both cases, you're dropping the first character of input. Presumably in the else case, you wanted the entire first argument.
see whether this construct is helpful for your purpose:
#!/bin/bash
name="longname55445"
echo "${name##*[A-Za-z]}"
this assumes a letter adjacent to number.
The following is NOT another way to write the same, because it is wrong.
Please see comments below by mklement0, who noticed this. Mea culpa.
echo "${name##*[:letter:]}"
You have command=${input:0:1}
It takes the first single char, and you compare it to "longname", of course it will fail, and go to elseif.
The key problem is to check if the input is beginning with l or longnameor nothing. If in one of the 3 cases, take the trailing numbers.
One grep line could do it, you can just grep on input and get the returned text:
kent$ grep -Po '(?<=longname|l|^)\d+' <<<"l234"
234
kent$ grep -Po '(?<=longname|l|^)\d+' <<<"longname234"
234
kent$ grep -Po '(?<=longname|l|^)\d+' <<<"234"
234
kent$ grep -Po '(?<=longname|l|^)\d+' <<<"foobar234"
<we got nothing>
You can use regex matching in bash.
[[ $1 =~ [0-9]+ ]] && number=$BASH_REMATCH
You can also use regex matching in zsh.
[[ $1 =~ [0-9]+ ]] && number=$MATCH
Based on the OP's following clarification in a comment,
I'm only looking for the numbers [...] given in the input.
the solution can be simplified as follows:
#!/bin/bash
BASE_URL='http://api.domain.com'
# Strip all non-digits from the 1st argument to get the desired number.
number=$(tr -dC '[:digit:]' <<<"$1")
open "$BASE_URL?id=$number"
Note the use of a bash shebang, given the use of 'bashism' <<< (which could easily be restated in a POSIX-compliant manner).
Similarly, the OP's original code should use a bash shebang, too, due to use of non-POSIX substring extraction syntax.
However, judging by the use of open to open a URL, the OP appears to be on OSX, where sh is essentially bash (though invocation as sh does change behavior), so it'll still work there. Generally, though, it's safer to be explicit about the required shell.

Manipulating data text file with bash command?

I was given this text file, call stock.txt, the content of the text file is:
pepsi;drinks;3
fries;snacks;6
apple;fruits;9
baron;drinks;7
orange;fruits;2
chips;snacks;8
I will need to use bash-script to come up this output:
Total amount for drinks: 10
Total amount for snacks: 14
Total amount for fruits: 11
Total of everything: 35
My gut tells me I will need to use sed, group, grep and something else.
Where should I start?
I would break the exercise down into steps
Step 1: Read the file one line at a time
while read -r line
do
# do something with $line
done
Step 2: Pattern match (drinks, snacks, fruits) and do some simple arithmetic. This step requires that you tokenized each line which I'll leave an exercise for you to figure out.
if [[ "$line" =~ "drinks" ]]
then
echo "matched drinks"
.
.
.
fi
Pure Bash. A nice application for an associative array:
declare -A category # associative array
IFS=';'
while read name cate price ; do
((category[$cate]+=price))
done < stock.txt
sum=0
for cate in ${!category[#]}; do # loop over the indices
printf "Total amount of %s: %d\n" $cate ${category[$cate]}
((sum+=${category[$cate]}))
done
printf "Total amount of everything: %d\n" $sum
There is a short description here about processing comma separated files in bash here:
http://www.cyberciti.biz/faq/unix-linux-bash-read-comma-separated-cvsfile/
You could do something similar. Just change IFS from comma to semicolon.
Oh yeah, and a general hint for learning bash: man is your friend. Use this command to see manual pages for all (or most) of commands and utilities.
Example: man read shows the manual page for read command. On most systems it will be opened in less, so you should exit the manual by pressing q (may be funny, but it took me a while to figure that out)
The easy way to do this is using a hash table, which is supported directly by bash 4.x and of course can be found in awk and perl. If you don't have a hash table then you need to loop twice: once to collect the unique values of the second column, once to total.
There are many ways to do this. Here's a fun one which doesn't use awk, sed or perl. The only external utilities I've used here are cut, sort and uniq. You could even replace cut with a little more effort. In fact lines 5-9 could have been written more easily with grep, (grep $kind stock.txt) but I avoided that to show off the power of bash.
for kind in $(cut -d\; -f 2 stock.txt | sort | uniq) ; do
total=0
while read d ; do
total=$(( total+d ))
done < <(
while read line ; do
[[ $line =~ $kind ]] && echo $line
done < stock.txt | cut -d\; -f3
)
echo "Total amount for $kind: $total"
done
We lose the strict ordering of your original output here. An exercise for you might be to find a way not to do that.
Discussion:
The first line describes a sub-shell with a simple pipeline using cut. We read the third field from the stock.txt file, with fields delineated by ;, written \; here so the shell does not interpret it. The result is a newline-separated list of values from stock.txt. This is piped to sort, then uniq. This performs our "grouping" step, since the pipeline will output an alphabetic list of items from the second column but will only list each item once no matter how many times it appeared in the input file.
Also on the first line is a typical for loop: For each item resulting from the sub-shell we loop once, storing the value of the item in the variable kind. This is the other half of the grouping step, making sure that each "Total" output line occurs once.
On the second line total is initialized to zero so that it always resets whenever a new group is started.
The third line begins the 'totaling' loop, in which for the current kind we find the sum of its occurrences. here we declare that we will read the variable d in from stdin on each iteration of the loop.
On the fourth line the totaling actually occurs: Using shell arithmatic we add the value in d to the value in total.
Line five ends the while loop and then describes its input. We use shell input redirection via < to specify that the input to the loop, and thus to the read command, comes from a file. We then use process substitution to specify that the file will actually be the results of a command.
On the sixth line the command that will feed the while-read loop begins. It is itself another while-read loop, this time reading into the variable line. On the seventh line the test is performed via a conditional construct. Here we use [[ for its =~ operator, which is a pattern matching operator. We are testing to see whether $line matches our current $kind.
On the eighth line we end the inner while-read loop and specify that its input comes from the stock.txt file, then we pipe the output of the entire loop, which by now is simply all lines matching $kind, to cut and instruct it to show only the third field, which is the numeric field. On line nine we then end the process substitution command, the output of which is a newline-delineated list of numbers from lines which were of the group specified by kind.
Given that the total is now known and the kind is known it is a simple matter to print the results to the screen.
The below answer is OP's. As it was edited in the question itself and OP hasn't come back for 6 years, I am editing out the answer from the question and posting it as wiki here.
My answer, to get the total price, I use this:
...
PRICE=0
IFS=";" # new field separator, the end of line
while read name cate price
do
let PRICE=PRICE+$price
done < stock.txt
echo $PRICE
When I echo, its :35, which is correct. Now I will moving on using awk to get the sub-category result.
Whole Solution:
Thanks guys, I manage to do it myself. Here is my code:
#!/bin/bash
INPUT=stock.txt
PRICE=0
DRINKS=0
SNACKS=0
FRUITS=0
old_IFS=$IFS # save the field separator
IFS=";" # new field separator, the end of line
while read name cate price
do
if [ $cate = "drinks" ]; then
let DRINKS=DRINKS+$price
fi
if [ $cate = "snacks" ]; then
let SNACKS=SNACKS+$price
fi
if [ $cate = "fruits" ]; then
let FRUITS=FRUITS+$price
fi
# Total
let PRICE=PRICE+$price
done < $INPUT
echo -e "Drinks: " $DRINKS
echo -e "Snacks: " $SNACKS
echo -e "Fruits: " $FRUITS
echo -e "Price " $PRICE
IFS=$old_IFS

Resources