Capturing first 4 and last 4 chars from a string in Bash? - bash

I have the following string,
szTemp="23.4 78.9"
I want to split it into two smaller strings,
szIndoorTemp="23.4"
szOutdoorTemp="78.9"
However doing this,
szIndoorTemp=${szTemp::-5}
szOutdoorTemp=${szTemp::5}
Does not produce this and causes the script to crash.
In addition, when I do
echo $szTemp
I get
23.4
78.9
I suspect that this may mean that there is a carriage return in the string.
Update
This may be close...
#!/bin/bash
szTemps="23.4 67.8"
szIndoorTemp=${szTemps:0:4}
szOutdoorTemp=${szTemps:5}
echo $szIndoorTemp
echo $szOutdoorTemp

Rather than depend on exact number of characters, I would go with relying on the space as a separator:
read szIndoorTemp szOutdoorTemp <<< "$szTemp"
As an alternative, you could do:
read szIndoorTemp szOutdoorTemp < <(yourPythonScript)

I think if I am getting it right here -5 could be the culprit, when I run command mentioned by you I see following error:
szIndoorTemp=${szTemp::-5}
bash: -5: substring expression < 0
But when I change from -5 to 5 it flies as follows:
szIndoorTemp=${szTemp::5}
echo $szIndoorTemp
23.4

Try this...
$ cat example.sh
szTemp="23.4 78.9"
echo "szIndoorTemp=${szTemp:0:4}"
echo "szOutdoorTemp=${szTemp:(-4)}"
$ sh example.sh
szIndoorTemp=23.4
szOutdoorTemp=78.9
You can also try this, if it works for you..
$ cat example.sh
szTemp="23.4 78.9"
szarray=($szTemp)
echo "szIndoorTemp=${szarray[0]}"
echo "szOutdoorTemp=${szarray[1]}"
$ sh example.sh
szIndoorTemp=23.4
szOutdoorTemp=78.9

I use filter, a function in my ~/.bashrc, because I need it so often, that the simple awk-solution is too complicated for me:) :
echo "23.4 78.9" | filter 1 2
23.4 78.9
And filter picks arguments in specified order by their index, counting from 1:
filter ()
{
declare -a arr;
while read val; do
arr=($val);
for param in "$#";
do
echo -n -e ${arr[$param-1]} "\t";
done;
echo;
done
}
Usage:
echo "23.4 78.9" | filter 1
23.4
echo "23.4 78.9" | filter 2
78.9
The equivalent operation in awk is:
echo $szTemp | awk '{print "$1"}'
23.4
echo $szTemp | awk '{print "$2"}'
78.9
but the curly braces are hard to type on a German keyboard; I guess we have the äüöß characters, where English keyboards have { and }.
Variable assignment becomes:
first=$(echo "23.4 78.9" | filter 1)
23.4
second=$(echo "23.4 78.9" | filter 2)
78.9

Related

assign two different variables from same line and loop for every line [duplicate]

This question already has answers here:
How to read variables from file, with multiple variables per line?
(2 answers)
Closed last month.
I am trying to assign variables obtained by awk, from a 2 columned txt file.
To a command, which includes every two value as two variables in it.
For example, the file I use is;
foo.txt
10 20
33 40
65 78
my command is aiming to print ;
end=20 start=10
end=40 start=33
end=78 start=65
Basically, I want to iterate the code for every line, and for output, there will be two variables from the two columns of the input file.
I am not an awk expert (I am trying my best), what I could have done so far is this fusion;
while read -r line ; do awk '{ second_variable=$2 ; first_variable=$1 ; }' ; echo "end=$first_name start=$second_name"; done <foo.txt
but it only gives this output;
end= start=
only one time without any variable. I would appreciate any suggestion. Thank you.
In bash you only need while, read and printf:
while read -r start end
do printf 'end=%d start=%d\n' "$end" "$start"
done < foo.txt
end=20 start=10
end=40 start=33
end=78 start=65
With awk, you could do:
awk '{print "end=" $2, "start=" $1}' foo.txt
end=20 start=10
end=40 start=33
end=78 start=65
With sed you'd use regular expressions:
sed -E 's/([0-9]+) ([0-9]+)/end=\2 start=\1/' foo.txt
end=20 start=10
end=40 start=33
end=78 start=65
Just in Bash:
while read -r end start; do echo "end=$end start=$start"; done <foo.txt
What about using xargs?
xargs -n2 sh -c 'echo end=$1 start=$2' sh < file.txt
Demo
xargs -n2 sh -c 'echo end=$1 start=$2' sh <<INPUT
10 20
33 40
65 78
INPUT
Output
end=10 start=20
end=33 start=40
end=65 start=78

Finding highest value of variable

So i am trying to find the highest value of the variable. For example o have this:
var1=14
var2=15
var3=16
I want to find the biggest value which is var 3 and save it somewhere. Is there a way to do that?
Something like this:
tmp=`sort -n $var1 $var2 $var3 ` (this is an example)
You'll need to get those numbers into an array, from there it's just:
a=(14 15 16) # Example array
IFS=$'\n'
echo "${a[*]}" | sort -nr | head -n1
This will find the max, by the variable names
#!/bin/bash
maxvarname() {
for i; do
echo "${!i} $i"
done | sort -nr | sed -n '1s/.* \(.*\)/\1/p'
}
#MAIN
#the variables
var1=14
var2=15
var3=16
vname=$(maxvarname var1 var2 var3) #note, arguments are the NAMES (not values e.g. $var1) - without $
echo "Max value is in the variable named: '$vname' and its value is: ${!vname}"
it prints:
Max value is in the variable named: 'var3' and its value is: 16
max=$(echo $var{1,2,3} | tr ' ' '\n' | sort -nr | head -1)
Check below solution if you want to find the maximum value of a variable -
$ cat f
var4=18
var1=14
var2=15
var3=16
$ max=$(sort -t'=' -nrk2 f|head -1)
$ echo $max
var4=18

for loop control in bash using a string

I want to use a string to control a for loop in bash. My first test code produces what I would expect and what I want:
$ aa='1 2 3 4'
$ for ii in $aa; do echo $ii; done
1
2
3
4
I'd like to use something like the following instead. This doesn't give the output I'd like (I can see why it does what it does).
$ aa='1..4'
$ for ii in $aa; do echo $ii; done
1..4
Any suggestions on how I should modify the second example to give the same output as the first?
Thanks in advance for any thoughts. I'm slowly learning bash but still have a lot to learn.
Mike
The notation could be written out as:
for ii in {1..4}; do echo "$ii"; done
but the {1..4} needs to be written out like that, no variables involved, and not as the result of variable substitution. That is brace expansion in the Bash manual, and it happens before string expansions, etc. You'll probably be best off using:
for ii in $(seq 1 4); do echo "$ii"; done
where either the 1 or the 4 or both can be shell variables.
You could use seq command (see man seq).
$ aa='1 4'
$ for ii in $(seq $aa); do echo $ii; done
Bash won't do brace expansion with variables, but you can use eval:
$ aa='1..4'
$ for ii in $(eval echo {$aa}); do echo $ii; done
1
2
3
4
You could also split aa into an array:
IFS=. arr=($aa)
for ((ii=arr[0]; ii<arr[2]; ii++)); do echo $ii; done
Note that IFS can only be a single character, so the .. range places the numbers into indexes 0 and 2.
Note There are certainly more elegant ways of doing this, as Ben Grimm's answer, and this is not pure bash, as uses seq and awk.
One way of achieving this is by calling seq. It would be trivial if you knew the numbers in the string beforehand, so there would be no need to do any conversion, as you could simple do seq 1 4 or seq $a $b for that matter.
I assume, however, that your input is indeed a string in the format you mentioned, that is, 1..4 or 20..100. For this purpose you could convert the string into 2 numbers ans use them as parameters for seq.
One of possibly many ways of achieving this is:
$ `echo "1..4" | sed -e 's/\.\./ /g' | awk '{print "seq", $1, $2}'`
1
2
3
4
Note that this will work the same way for any input in the given format. If desired, sed can be changed by tr with similar results.
$ x="10..15"
$ `echo $x | tr "." " " | awk '{print "seq", $1, $2}'`
10
11
12
13
14
15

How do I pick random unique lines from a text file in shell?

I have a text file with an unknown number of lines. I need to grab some of those lines at random, but I don't want there to be any risk of repeats.
I tried this:
jot -r 3 1 `wc -l<input.txt` | while read n; do
awk -v n=$n 'NR==n' input.txt
done
But this is ugly, and doesn't protect against repeats.
I also tried this:
awk -vmax=3 'rand() > 0.5 {print;count++} count>max {exit}' input.txt
But that obviously isn't the right approach either, as I'm not guaranteed even to get max lines.
I'm stuck. How do I do this?
This might work for you:
shuf -n3 file
shuf is one of GNU coreutils.
If you have Python accessible (change the 10 to what you'd like):
python -c 'import random, sys; print("".join(random.sample(sys.stdin.readlines(), 10)).rstrip("\n"))' < input.txt
(This will work in Python 2.x and 3.x.)
Also, (again change the 10 to the appropriate value):
sort -R input.txt | head -10
If jot is on your system, then I guess you're running FreeBSD or OSX rather than Linux, so you probably don't have tools like rl or sort -R available.
No worries. I had to do this a while ago. Try this instead:
$ printf 'one\ntwo\nthree\nfour\nfive\n' > input.txt
$ cat rndlines
#!/bin/sh
# default to 3 lines of output
lines="${1:-3}"
# default to "input.txt" as input file
input="${2:-input.txt}"
# First, put a random number at the beginning of each line.
while read line; do
printf '%8d%s\n' $(jot -r 1 1 99999999) "$line"
done < "$input" |
sort -n | # Next, sort by the random number.
sed 's/^.\{8\}//' | # Last, remove the number from the start of each line.
head -n "$lines" # Show our output
$ ./rndlines input.txt
two
one
five
$ ./rndlines input.txt
four
two
three
$
Here's a 1-line example that also inserts the random number a little more cleanly using awk:
$ printf 'one\ntwo\nthree\nfour\nfive\n' | awk 'BEGIN{srand()} {printf("%8d%s\n", rand()*10000000, $0)}' | sort -n | head -n 3 | cut -c9-
Note that different versions of sed (in FreeBSD and OSX) may require the -E option instead of -r to handle ERE instead or BRE dialect in the regular expression if you want to use that explictely, though everything I've tested works with escapted bounds in BRE. (Ancient versions of sed (HP/UX, etc) might not support this notation, but you'd only be using those if you already knew how to do this.)
This should do the trick, at least with bash and assuming your environment has the other commands available:
cat chk.c | while read x; do
echo $RANDOM:$x
done | sort -t: -k1 -n | tail -10 | sed 's/^[0-9]*://'
It basically outputs your file, placing a random number at the start of each line.
Then it sorts on that number, grabs the last 10 lines, and removes that number from them.
Hence, it gives you ten random lines from the file, with no repeats.
For example, here's a transcript of it running three times with that chk.c file:
====
pax$ testprog chk.c
} else {
}
newNode->next = NULL;
colm++;
====
pax$ testprog chk.c
}
arg++;
printf (" [%s] n", currNode->value);
free (tempNode->value);
====
pax$ testprog chk.c
char tagBuff[101];
}
return ERR_OTHER;
#define ERR_MEM 1
===
pax$ _
sort -Ru filename | head -5
will ensure no duplicates. Not all implementations of sort have the -R option.
To get N random lines from FILE with Perl:
perl -MList::Util=shuffle -e 'print shuffle <>' FILE | head -N
Here's an answer using ruby if you don't want to install anything else:
cat filename | ruby -e 'puts ARGF.read.split("\n").uniq.shuffle.join("\n")'
for example, given a file (dups.txt) that looks like:
1 2
1 3
2
1 2
3
4
1 3
5
6
6
7
You might get the following output (or some permutation):
cat dups.txt| ruby -e 'puts ARGF.read.split("\n").uniq.shuffle.join("\n")'
4
6
5
1 2
2
3
7
1 3
Further example from the comments:
printf 'test\ntest1\ntest2\n' | ruby -e 'puts ARGF.read.split("\n").uniq.shuffle.join("\n")'
test1
test
test2
Of course if you have a file with repeated lines of test you'll get just one line:
printf 'test\ntest\ntest\n' | ruby -e 'puts ARGF.read.split("\n").uniq.shuffle.join("\n")'
test

Bash and minus floating points

I have code like this in Bash :
read a
read b
c=96.0
d=100.0
echo "scale=2;($b*$c - $a*$d)/$a" |bc
And it prints result of this expression :
(b*96-a*100)/a
But when result is between -1 and 0
it gives something like this : -.99
For smaller values it prints result correctly.
So, my question is, how to force program to put 0 when printing
0.123123(...)
? Not only
.123123(...)
Use printf:
$ printf "%0.2f" "$(echo 'scale=2; 1.9/10.0' | bc)"
0.19
printf is (also) a bash builtin

Resources