I am new to bash and writing a script that needs to compare the minor version of the kernel to see if it is greater than or equal to 10, and exit if it is not. Currently I have something like this:
KERNEL=$(uname -r)
declare -i MINOR_VERSION=$(echo $KERNEL | cut -c 3-4)
if [ "10" -gt "$MINOR_VERSION"]; then exit 0; fi
This is bad code, and doesn't work if the minor version is < 10 since I am using cut and depending on it being two digits. What I probably need is something that parses the minor version by using the dots.
Example:
$ uname -r
3.4.0-60-generic
$ MNR_VAR=<awesome bash code, with cut or sed or something>
$ echo $MNR_VAR
4
I have been reading cut and sed documentation but have just been slow picking it up. I would appreciate the help!
TL;DR - looking for a bash command that will extract an int surrounded by the first two dots in a variable. "3.13.0.x" returns '13', "3.2.0.x" returns '2', etc.
EDIT:
Some answers as one liners below for those curious.
uname -r | cut -d '.' -f2
uname -r | awk -F . '{print $2}'
kernel="$(uname -r)" | tmp="${kernel#*.}" | minor="${tmp%%.*}" | echo "$minor"
In pure bash:
#!/bin/bash
ker="$(uname -r)"
minker="${ker#*.}"
minker="${minker%%.*}"
echo "$minker"
"${ker#*.}" is the string after the first match of a . in $ker. Thus
$minker becomes 13.0-generic... from 3.13.0-generic...
"${minker%%.*}" is the string left by cutting all matches (from right) of . and whatever after it, in $minker. Thus $minker becomes 13 from 13.0-generic...
See the Bash Parameter Expansion Manual for more info
Using Bash Regex:
#!/bin/bash
regex='([0-9]+)\.([0-9]+)'
[[ $(uname -r) =~ $regex ]]
echo ${BASH_REMATCH[2]}
The problem is you are using -c to cut. Don't do that.
Use the -f and -d flags instead to control the delimiter and fields to output.
Or use awk -F . '{print $2}' <<< "$(uname -r)".
Or use IFS=. read -r _ minor _rest <<< "$(uname -r)"; echo "$minor" (which has the benefit of not using any external utilities).
The usage of <<< "$(uname -r)" is bash-specific (I believe) but avoids the need for a pipe (|) and the sub-shell that it involves.
Extracting just minor version & comparing it with something is risky, because major number can change too...
I normally prefer padding the numbers with zeros, so that they can be easily compared using simple string compare.
kernel_version=$(uname -r | sed -r 's/([0-9]+)/0000\1/g; s/0*([0-9]{4})/\1/g') # gives 0003.0004.0000-0060-generic
if [[ "$kernel_version" < "0003.0010" ]]; then exit 0; fi
Related
Environment:
Linux Mint, Cinnamon desktop manager, with multiple workspaces=virtual desktops, e.g. 4.
Bash script
What is known:
How to determine the number of workspaces:
wmctrl -d | wc -l
What I need:
Get the number of virtual desktops the bash script is running on with a pure bash as var (like with grep, not awk or similar) and echo the var.
With awk (imho still the most appropriate choice for the task at hand):
nr_of_active_workspace=$(wmctrl -d | awk '/\*/{print $NF}')
echo $nr_of_active_workspace
Or a pure bash solution:
nr_of_active_workspace=$(wmctrl -d | while read -r line; do [[ $line =~ '*' ]] && echo ${line: -1} ; done)
echo $nr_of_active_workspace
You can use POSIX shell features and the xprop(1) command to get both details with no other external utilities.
To get the ID number of the current/active desktop:
curdesk=$(xprop -root -notype _NET_CURRENT_DESKTOP)
curdesk="${curdesk##* }"
To get the count/quantity of desktops defined:
deskcnt=$(xprop -root -notype _NET_NUMBER_OF_DESKTOPS)
deskcnt="${deskcnt##* }"
Both depend on xprop(1) giving the answer in the form "foo = 0" (separated by spaces), and use shell pattern matching parameter expansion to match the longest substring ending in space, and remove it, leaving only the last token (the value after the equals sign).
Note that desktops are numbered from 0 (zero), so the count will be a number one higher than the ID number of the last desktop.
This should work with any window manager that adheres to the Extended Window Manager Hints (EWMH) specification (which is practically all of them, these days):
https://specifications.freedesktop.org/wm-spec/1.3/ar01s03.html
Follow a solution which need awk:
nr_of_active_workspace=$(wmctrl -d | grep "*" | awk '{print $11}')
echo $nr_of_active_workspace
It can be a solution without need awk, are possible on other way.
Based on answer of KamilCuk, its possible to output on follow way the line which is including the number of the active desktop:
nr_of_active_desktop=activedesktop=$(wmctrl -d | grep "*" | rev | cut -d ' ' -f1)
echo $nr_of_active_desktop
I have the following grep:
grep -Po '(?<=PROGRAM\()[^\)]+(?=\))' /home/programs/hello_word.sh
Wich displays the string between PROGRAM( and ):
RECTONTER
Then, I need to know if these string extracted is contained in a file, so:
grep -Po '(?<=PROGRAM\()[^\)]+(?=\))' /home/programs/hello_word.sh | xargs -I % grep -e % /home/leherad/pgm_currentdate
File content:
RECTONTER
CORASFE
RENTOASD
UBICARP
If its found, returns the line of /home/leherad/pgm_currentdate, but I want to print the line extracted in the first grep (RECTONTER). If not found, then wouldn't return nothing.
There is a simple way to do this, or I should not complicate and would be better build a script and save the first grep in a variable?
You can store it on a variable first:
read -r FIRST < <(exec grep -Po '(?<=PROGRAM\()[^\)]+(?=\))' /home/programs/hello_word.sh) && grep -e "$FIRST" /home/leherad/pgm_currentdate
Update 01
#!/bin/bash
shopt -s nullglob
for FILE in /home/programs/*; do
read -r FIRST < <(exec grep -Po '(?<=PROGRAM\()[^\)]+(?=\))' "$FILE") && grep -e "$FIRST" /home/leherad/pgm_currentdate && echo "$FIRST"
done
I think a straightforward way to solve this is to use a function.
Also, your grep pattern will match shell comments, which could cause unexpected behavior in your xargs command when there are more than one matches; you might want to take steps to only grab the first match. It's hard to say without actually seeing the input files, so I'm guessing this is either ok or comments are actually the expected place for your target pattern.
Anyway, here's my best guess at a function that would work for you.
get_program() {
local filename="$1"
local program="$( grep -m1 -Po '(?<=PROGRAM\()[^\)]+(?=\))' "$filename" )"
if grep -q -e "$program" /home/leherad/pgm_currentdate; then
echo $program
grep -e "$program" /home/leherad/pgm_currentdate
fi
}
get_program /home/programs/hello_word.sh
I have a bunch of files that all have a name and a serial number and an extension. I want to extract this serial number and extension. They look like this:
photo-123.jpg
photo-456.png
photo-789.bmp
etc.
I want to run a bash script to extract these serial numbers and place them in a file in this way:
123
456
789
etc.
Note that not all the photos have the same extension (bmp, png, jpg) but they all start with photo-.
You can use parameter substitution:
$ ls
photo-123.jpg photo-456.png photo-7832525239.bmp photo-789.bmp
$ for file in *; do
[[ -f "$file" ]] || continue
[[ $file == "num.log" ]] && continue
file=${file%.*} && echo "${file#*-}"
done > num.log
$ ls
num.log photo-123.jpg photo-456.png photo-7832525239.bmp photo-789.bmp
$ cat num.log
123
456
7832525239
789
${parameter#word} removes the shortest match from the start and ${parameter##word} removes the longest match from the start. ${parameter%word} on the contrary will remove shortest match from the end and ${parameter%%word} will remove longest match from the end.
Alternatively, you can read about nullglob instead of checking for existence of file in event there are no files in the directory. (Thanks Adrian Frühwirth for great feedback)
Using BASH regex:
f='photo-123.jpg'
[[ "$f" =~ -([0-9]+)\. ]] && echo "${BASH_REMATCH[1]}"
123
To run it against all the matching files:
for f in *-[0-9]*.*; do
[[ "$f" =~ -([0-9]+)\. ]] && echo "${BASH_REMATCH[1]}"
done
Assuming you just want to keep all of the numbers and you're using bash, here are a couple of things which you may find useful:
danny#machine:~$ file=abc123def.jpg
danny#machine:~$ echo ${file//[^0123456789]/}
123
danny#machine:~$ echo ${file##*.}
jpg
danny#machine:~$ echo ${file//[^0123456789]/}.${file##*.}
123.jpg
You should be able to write your script based on that. Or, just remove the leading "photo-" from $name by using
newname=$(name#photo-}
Those and several others are explained in the bash man page's Parameter Expansion section.
Or maybe with two consecutive awk calls:
ls -1 | awk -F- '{print $2}' | awk -F. '{print $1}'
How about
ls -l | awk {'print $9'} | grep -o -E '[0-9]*'
in the directory where the files reside?
How can i only output that yes or no by skiping anything?
$ pactl list sources | grep -A 6 "Name: alsa_input.usb.analog-mono" | tail -n 1 | perl -p -i -e 's/.+\\s(yes|no)$/$1/'
Mute: no
insead show only: no
Your perl expression is broken. Observe:
$ echo "Mute: no" | perl -p -i -e 's/.+\\s(yes|no)$//'
Mute: no
It doesn't do anything! The reason it doesn't do anything is because nothing is matching. Why not? Because you unnecessarily escaped \s. The correct expression should be s/.+\s(yes|no)$/$1/. Notice the single \ preceding s.
With this new expression we get:
$ echo "Mute: no" | perl -p -i -e 's/.+\s(yes|no)$/$1/'
no
$ echo "Mute: yes" | perl -p -i -e 's/.+\s(yes|no)$/$1/'
yes
Note that this still doesn't do what you want, because if nothing matches, the whole line is emitted as is. For example:
$ echo "Mute: monkies" | perl -p -e 's/.+\s(yes|no)$/$1/'
Mute: monkies
While this can probably solved with a more complex regular expression, I will not do so. Instead I will quote the following:
Sometimes when people see a problem, they think to themselves "I
know, I will use regex." Now they have two problems.
perl -M5.010 -ne 'say $1 if m/\s(yes|no)$/i'
This works:
LANG=C pactl list sources | perl -0 -lne'print $1 if /Name: alsa_input.usb.analog-mono\n(?:\N+\n){5}\s+Mute: (yes|no)/ms'
But you need to parse this properly, you cannot rely on the Mute line always coming exactly 6 lines after.
from perl perspective:
perl -p -i -e 's/.+\\s(yes|no)$/$1/is'
Please insert the raw input and what do you want achive (expected output)
How do I use grep to perform a search which, when a match is found, will print the file name as well as the first n characters in that file? Note that n is a parameter that can be specified and it is irrelevant whether the first n characters actually contains the matching string.
grep -l pattern *.txt |
while read line; do
echo -n "$line: ";
head -c $n "$line";
echo;
done
Change -c to -n if you want to see the first n lines instead of bytes.
You need to pipe the output of grep to sed to accomplish what you want. Here is an example:
grep mypattern *.txt | sed 's/^\([^:]*:.......\).*/\1/'
The number of dots is the number of characters you want to print. Many versions of sed often provide an option, like -r (GNU/Linux) and -E (FreeBSD), that allows you to use modern-style regular expressions. This makes it possible to specify numerically the number of characters you want to print.
N=7
grep mypattern *.txt /dev/null | sed -r "s/^([^:]*:.{$N}).*/\1/"
Note that this solution is a lot more efficient that others propsoed, which invoke multiple processes.
There are few tools that print 'n characters' rather than 'n lines'. Are you sure you really want characters and not lines? The whole thing can perhaps be best done in Perl. As specified (using grep), we can do:
pattern="$1"
shift
n="$2"
shift
grep -l "$pattern" "$#" |
while read file
do
echo "$file:" $(dd if="$file" count=${n}c)
done
The quotes around $file preserve multiple spaces in file names correctly. We can debate the command line usage, currently (assuming the command name is 'ngrep'):
ngrep pattern n [file ...]
I note that #litb used 'head -c $n'; that's neater than the dd command I used. There might be some systems without head (but they'd pretty archaic). I note that the POSIX version of head only supports -n and the number of lines; the -c option is probably a GNU extension.
Two thoughts here:
1) If efficiency was not a concern (like that would ever happen), you could check $status [csh] after running grep on each file. E.g.: (For N characters = 25.)
foreach FILE ( file1 file2 ... fileN )
grep targetToMatch ${FILE} > /dev/null
if ( $status == 0 ) then
echo -n "${FILE}: "
head -c25 ${FILE}
endif
end
2) GNU [FSF] head contains a --verbose [-v] switch. It also offers --null, to accomodate filenames with spaces. And there's '--', to handle filenames like "-c". So you could do:
grep --null -l targetToMatch -- file1 file2 ... fileN |
xargs --null head -v -c25 --