Given String: approximateLastUseTime: '2019-10-15T16:56:07.082500Z', I need to extract everything after the T and before the Z (after the colon) - bash

Given String:->
approximateLastUseTime: '2019-10-15T16:56:07.082500Z'
I need to extract everything after the T and before the Z (after the colon)

With bash, you can use substring operator, or prefix/suffix
T=2019-10-15T16:56:07.082500Z
# Using substring
echo ${T:11:15}
# Using prefix/suffix
T1=${T#*T}
T1=${T1%Z*}
echo "$T1"

Related

How to convert a semantic version shell variable to a shifted integer?

Given a shell variable whose value is a semantic version, how can I create another shell variable whose value is (tuple 1 × 1000000) + (tuple 2 × 1000) + (tuple 3) ?
E.g.
$ FOO=1.2.3
$ BAR=#shell magic that, given ${FOO} returns `1002003`
# Shell-native string-manipulation? sed? ...?
I'm unclear about how POSIX-compliance vs. shell-specific syntax comes into play here, but I think a solution not bash-specific is preferred.
Update: To clarify: this isn't as straightforward as replacing "." with zero(es), which was my initial thought.
E.g. The desired output for 1.12.30 is 1012030, not 100120030, which is what a .-replacement approach might provide.
Bonus if the answer can be a one-liner variable-assignment.
A perl one-liner:
echo $FOO | perl -pne 's/\.(\d+)/sprintf "%03d", $1/eg'
How it works:
perl -pne does a REPL with the supplied program
The program contains a replacement function s///
The search string is the regex \.(\d+) which matches a string beginning with dot and ends with digits and capture those digits
The e modifier of the s/// function evaluates the right-hand side of the s/// replacement as an expression. Since we captured the digits, they'll be converted into int and formatted into leading zeros with sprintf
The g modifier replaces all instances of the regex in the input string
Demo
Split on dots, then loop and multiply/add:
version="1.12.30"
# Split on dots instead of spaces from now on
IFS="."
# Loop over each number and accumulate
int=0
for n in $version
do
int=$((int*1000 + n))
done
echo "$version is $int"
Be aware that this treats 1.2 and 0.1.2 the same. If you want to always treat the first number as major/million, consider padding/truncating beforehand.
This should do it
echo $foo | sed 's/\./00/g'
How about this?
$ ver=1.12.30
$ foo=$(bar=($(echo $ver|sed 's/\./ /g')); expr ${bar[0]} \* 1000000 + ${bar[1]} \* 1000 + ${bar[2]})
$ echo $foo
1012030

Extract value for a key in a key/pair string

I have key value pairs in a string like this:
key1 = "value1"
key2 = "value2"
key3 = "value3"
In a bash script, I need to extract the value of one of the keys like for key2, I should get value2, not in quote.
My bash script needs to work in both Redhat and Ubuntu Linux hosts.
What would be the easiest and most reliable way of doing this?
I tried something like this simplified script:
pattern='key2\s*=\s*\"(.*?)\".*$'
if [[ "$content" =~ $pattern ]]
then
key2="${BASH_REMATCH[1]}"
echo "key2: $key2"
else
echo 'not found'
fi
But it does not work consistently.
Any better/easier/more reliable way of doing this?
To separate the key and value from your $content variable, you can use:
[[ $content =~ (^[^ ]+)[[:blank:]]*=[[:blank:]]*[[:punct:]](.*)[[:punct:]]$ ]]
That will properly populate the BASH_REMATCH array with both values where your key is in BASH_REMATCH[1] and the value in BASH_REMATCH[2].
Explanation
In bash the [[...]] treats what appears on the right side of =~ as an extended regular expression and matched according to man 3 regex. See man 1 bash under the section heading for [[ expression ]] (4th paragraph). Sub-expressions in parenthesis (..) are saved in the array variable BASH_REMATCH with BASH_REMATCH[0] containing the entire portion of the string (your $content) and each remaining elements containing the sub-expressions enclosed in (..) in the order the parenthesis appear in the regex.
The Regular Expression (^[^ ]+)[[:blank:]]*=[[:blank:]]*[[:punct:]](.*)[[:punct:]]$ is explained as:
(^[^ ]+) - '^' anchored at the beginning of the line, [^ ]+ match one or more characters that are not a space. Since this sub-expression is enclosed in (..) it will be saved as BASH_REMATCH[1], followed by;
[[:blank:]]* - zero or more whitespace characters, followed by;
= - an equal sign, followed by;
[[:blank:]]* - zero or more whitespace characters, followed by;
[[:punct:]] - a punctuation character (matching the '"', which avoids caveats associated with using quotes within the regex), followed by the sub-expression;
(.*) - zero or more characters (the rest of the characters), and since it is a sub-expression in (..) it the characters will be stored in BASH_REMATCH[2], followed by;
[[:punct:]] - a punctuation character (matching the '"' ... ditto), at the;
$ - end of line anchor.
So if you match what your key and value input lines separated by an = sign, it will separate the key and value into the array BASH_REMATCH as you wanted.
Bash supports BRE only and you cannot use \s and .*?.
As an alternative, please try:
while IFS= read -r content; do
# pattern='key2\s*=\s*\"(.*)\".*$'
pattern='key2[[:blank:]]*=[[:blank:]]*"([^"]*)"'
if [[ $content =~ $pattern ]]
then
key2="${BASH_REMATCH[1]}"
echo "key2: $key2"
(( found++ ))
fi
done < input-file.txt
if (( found == 0 )); then
echo "not found"
fi
What you start talking about key-value pairs, it is best to use an associative array:
declare -A map
Now looking at your lines, they look like key = "value" where we assume that:
value is always encapsulated by double quotes, but also could contain a quote
an unknown number of white spaces is before and/or after the equal sign.
So assuming we have a variable line which contains key = "value", the following operations will extract that value:
key="${line%%=*}"; key="${key// /}"
value="${line#*=}"; value="${value#*\042}"; value="${value%\042*}"
IFS=" \t=" read -r value _ <<<"$line"
This allows us now to have something like:
declare -A map
while read -r line; do
key="${line%%=*}"; key="${key// /}"
value="${line#*=}"; value="${value#*\042}"; value="${value%\042*}"
map["$key"]="$value"
done <inputfile
With awk:
awk -v key="key2" '$1 == key { gsub("\"","",$3);print $3 }' <<< "$string"
Reading the output of the variable called string, pass the required key in as a variable called key and then if the first space delimited field is equal to the key, remove the quotes from the third field with the gsub function and print.
Ok, after spending so many hours, this is how I solved the problem:
If you don't know where your script will run and what type of file (win/mac/linux) are you reading:
Try to avoid non-greedy macth in linux bash instead of tweaking diffrent switches.
don't trus end of line match $ when you might get data from windows or mac
This post solved my problem: Non greedy text matching and extrapolating in bash
This pattern works for me in may linux environments and all type of end of lines:
pattern='key2\s*=\s*"([^"]*)"'
The value is in BASH_REMATCH[1]

Replace Last Occurrence of Substring in String (bash)

From the bash software manual:
${parameter/pattern/string}
The pattern is expanded to produce a
pattern just as in filename expansion. Parameter is expanded and the
longest match of pattern against its value is replaced with string.
... If pattern begins with ‘%’, it must match
at the end of the expanded value of parameter.
And so I've tried:
local new_name=${file/%old/new}
Where string is an absolute file path (/abc/defg/hij and old and new are variable strings.
However this seems to be trying to match the literal %sb1.
What is the syntax for this?
Expected Output:
Given
old=sb1
new=sb2
Then
/foo/sb1/foo/bar/sb1 should become /foo/sb1/foo/bar/sb2
/foo/foosb1other/foo/bar/foosb1bar should become /foo/foosb1other/foo/bar/foosb2bar
Using only shell-builtin parameter expansion:
src=sb1; dest=sb2
old=/foo/foosb1other/foo/bar/foosb1bar
if [[ $old = *"$src"* ]]; then
prefix=${old%"$src"*} # Extract content before the last instance
suffix=${old#"$prefix"} # Extract content *after* our prefix
new=${prefix}${suffix/"$src"/"$dest"} # Append unmodified prefix w/ suffix w/ replacement
else
new=$old
fi
declare -p new >&2
...properly emits:
declare -- new="/foo/foosb1other/foo/bar/foosb2bar"

shell script to add leading zeros in middle of file name

I have files with names like "words_transfer1_morewords.txt". I would like to ensure that the number after "transfer" is five digits, as in "words_transfer00001_morewords.txt". How would I do this with a ksh script? Thanks.
This will work in any Bourne-type/POSIX shell as long as your words and morewords don't contain numbers:
file=words_transfer1_morewords.txt
prefix=${file%%[0-9]*} # words_transfer
suffix=${file##*[0-9]} # _morewords.txt
num=${file#$prefix} # 1_morewords.txt
num=${num%$suffix} # 1
file=$(printf "%s%05d%s" "$prefix" "$num" "$suffix")
echo "$file"
Use ksh's regular expression matching operation to break the filename down into separate parts, them put them back together again after formatting the number.
pre="[^[:digit:]]+" # What to match before the number
num="[[:digit:]]+" # The number to match
post=".*" # What to match after the number
[[ $file =~ ($pre)($num)($post) ]]
new_file=$(printf "%s%05d%s\n" "${.sh.match[#]:1:3}")
Following a successful match with =~, the special array parameter .sh.match contains the full match in element 0, and each capture group in order starting with element 1.

How to split string into component parts in Linux Bash/Shell

I'm writing the second version of my post-receive git hook.
I have a GL_REPO variable which conforms to:
/project.name/vhost-type/versioncodename
It may or may not have a trailing and/or preceding slash.
My current code misunderstood the function of the following code, and as a result it clearly duplicates $versioncodename into each variable:
# regex out project codename
PROJECT_NAME=${GL_REPO##*/}
echo "project codename is: $PROJECT_NAME"
# extract server target vhost-type -fix required
VHOST_TYPE=${GL_REPO##*/}
echo "server target is: $VHOST_TYPE"
# get server project - fix required
PROJECT_CODENAME=${GL_REPO##*/}
echo "server project is: $PROJECT_CODENAME"
What is the correct method for taking these elements one at a time from the back of the string, or guaranteeing that a three part string allocates these variables?
I guess it might be better to split into an array?
#!/bin/bash
GL_REPO=/project.name/vhost-type/versioncodename
GL_REPO=${GL_REPO#/} # remove preceding slash, if any
IFS=/ read -a arr <<< "$GL_REPO"
PROJECT_NAME="${arr[0]}"
VHOST_TYPE="${arr[1]}"
PROJECT_CODENAME="${arr[2]}"
UPDATE: an alternative solution by anishsane:
IFS=/ read PROJECT_NAME VHOST_TYPE PROJECT_CODENAME <<< "$GL_REPO"
You can use cut with a field separator to pull out items by order:
NAME=$(echo $GL_REPO | cut -d / -f 1)
You can repeat the same for other fields. The trailing/leading slash you can ignore (you'll get a NAME field being empty, for example) or you can strip off a leading slash with ${GL_REPO##/} (similarly, you can strip off a trailing slash with ${GL_REPO%%/}).
This is another way:
GL_REPO="/project.name/vhost-type/versioncodename"
GL_REPO="${GL_REPO/#\//}"
#^replace preceding slash (if any) with empty string.
IFS="/" arr=($GL_REPO)
echo "PN: ${arr[0]} VHT: ${arr[1]} VC: ${arr[2]}"
Using Bash Pattern Matching:
GL_REPO="/project.name/vhost-type/versioncodename"
patt="([^/]+)/([^/]+)/([^/]+)"
[[ $GL_REPO =~ $patt ]]
echo "PN: ${BASH_REMATCH[1]} VHT: ${BASH_REMATCH[2]} VC: ${BASH_REMATCH[3]}"

Resources