I have a variable in a GNU make file :
VAR=DDC
I want to merge these characters into another string and assign them to a different variable, for example:
TOT_VAR=--'D','D','C'--
My first idea was to do something like this pseudocode:
#Pseudo code
TOT_VAR=--'#(letter1, VAR)','#(letter2, VAR)','#(letter3, VAR)'--
But I can't find any function that extracts individual characters.
How might I do this?
You could use sed to do the transformation:
echo DDC | sed -E "s/^(.{1})(.{1})(.{1}).*/--'\1','\2','\3'--/")
What this does:
Pipes the string DDC to sed
Tells sed to parse the string and match the first three characters
Substitute DDC for your replacement format, inserting those three characters into the placeholders \1, \2 and \3
[If you’re on a Linux OS rather than MacOS like me then I think you’ll need to use sed -e rather than sed -E]
As the comment from #Maxim says, you'll need to invoke the shell from make in order to run this command. Building this approach into a simple makefile to illustrate:
VAR=DDC
TOT_VAR:=$(shell echo $(VAR) | sed -E "s/^(.{1})(.{1})(.{1}).*/--'\1','\2','\3'--/")
all:
echo "$(TOT_VAR)"
Running make all on this yields this output and shows the substitution has worked:
echo "--'D','D','C'--"
--'D','D','C'--
The GNU table toolkit has a function explode which expands a string into a list:
$(call explode,stringlist,string)
Insert a blank after every occurrence of the strings from stringlist in string. This function serves mainly to convert a string into a list:
$(call explode,0 1 2 3 4 5 6 7 8 9,0x1337c0de) --> 0 x1 3 3 7 c0 de
You can access the members of your variable like the following:
include gmtt/gmtt.mk
XPLD_VAR := $(call explode,$([alnum]),$(VAR))
TOT_VAR := --$(word 1,$(XPLD_VAR)),$(word 2,$(XPLD_VAR)),$(word 3,$(XPLD_VAR))--
Related
I got this x.xx.xxx.xxxx.api-6.8.25-SNAPSHOT.jar filename, which I would like to change to, x.xx.xxx.xxxx.api_6.8.25.SNAPSHOT.jar. using sed I came up with this:
FILENAME=$(sed 's/-(?=[\w])/_/g' <<< "$FILENAME")
The regex pattern seems to be correct in pointing -s, however when my script runs no change is applied on my string. what I'm missing here? and how can I have multiple substitutions? changing the first dash with an underscore and the second with a dot?
I suggest:
echo 'x.xx.xxx.xxxx.api-6.8.25-SNAPSHOT.jar' | sed 's/-/_/; s/-/./'
Output:
x.xx.xxx.xxxx.api_6.8.25.SNAPSHOT.jar
Pure bash solution without calling any external utility:
fn='xx.xxx.xxxx.api-6.8.25-SNAPSHOT.jar`
fn="${fn/-/_}" # replace first - by _
fn="${fn/-/.}" # replace next - by .
echo "$fn"
xx.xxx.xxxx.api_6.8.25.SNAPSHOT.jar
You can use
FILENAME=$(sed -E 's/(.*)-([0-9.]+)-/\1_\2./' <<< "$FILENAME")
See the online demo.
Details:
-E enables POSIX ERE syntax
(.*)-([0-9.]+)- - a regex that matches and captures into Group 1 any zero or more chars, then -, then one or more digits or dots captured into Group 2 and then a -
\1_\2. is the replacement, Group 1, _, Group 2 and a ..
I do not want to replace just any letters with words. I only want to replace letters that are stand alone with exact case sensitive matches.
I have a file that looks like this:
file>-string pile stage one on one/a>b
system > use while one is one stage/sump/f
get sole--> one by one dir/sub/main/c >b
multi string return one by one sub//c static a
ect...
ect...
I want to replace the letter that are not part of word.
a=ASTRING
b=BSTRING
c=CSTRING
f=FSTRING
The desired output would look like:
file>-string pile stage one on one/ASTRING>BSTRING
system > use while one is one stage/sump/FSTRING
get sole--> one by one dir/sub/main/CSTRING >BSTRING
multi string return one by one sub//CSTRING static ASTRING
ect...
ect...
Here is what I have been trying:
sed -i "s|a|ASTRING|g" file.txt
grep [a] file.txt | sed -i "s|a|ASTRING|g"
Repeated for b c f
Use \b in the regular expression to match a word boundary.
sed -i 's/\ba\b/ASTRING/g' file.txt
BTW, you can't use -i if you're piping the input to sed, the filename has to be an argument to sed. If it gets its input from standard input, it doesn't know the filename so it can't update the file in place.
nameref variables to the rescue.
a=ASTRING
b=BSTRING
c=CSTRING
f=FSTRING
for v in a b c f
do declare -n R=$v
sed -Ei "s/\\b$v\\b/$R/g" file.txt
done
I would like make a bash script to update existing version number to a given one
The version number in the code is:
test_1_2_3_456
The patter would be
test_#_#_#_###
And I would like to replace it with a new one through bash argument like
7_0_0_123
The attampt would be
/my_script 7_0_0_123
And inside my_script, it can find the pattern and replace with 7_0_0_123
I think this will help you:
new=7_0_0_123
echo test_1_2_3_456 | sed -E 's/test_[0-9]_[0-9]_[0-9]_[0-9]{3}/test_'$new'/'
test_7_0_0_123
with :
-E : Interpret regular expressions as extended (modern) regular expressions rather than basic regular
[0-9] matches 1 digit
[0-9]{3} matches 3 digits (no less no more)
'$new' the new string you want to use
you can write it in more compact way
$ echo test_1_2_3_456 | sed -r 's/(test)(_[0-9]){4}[0-9]{2}/\1_7_0_0_123/'
test_7_0_0_123
Explanation: match the pattern "test" followed by 4 copies of "_" followed by single digit and 2 additional digits, replace with first grouped match (which is "test") and desired version number. This eliminates repeating "test", need -r option for regex features.
With GNU sed:
sed -r 's/^test_[0-9]_[0-9]_[0-9]_[0-9]{3}$/7_0_0_123/' file.txt
Example:
% sed -r 's/^test_[0-9]_[0-9]_[0-9]_[0-9]{3}$/7_0_0_123/' <<<'test_1_2_3_456'
7_0_0_123
Input:-
echo "1234ABC89,234" # A
echo "0520001DEF78,66" # B
echo "46545455KRJ21,00"
From the above strings, I need to split the characters to get the alphabetic field and the number after that.
From "1234ABC89,234", the output should be:
ABC
89,234
From "0520001DEF78,66", the output should be:
DEF
78,66
I have many strings that I need to split like this.
Here is my script so far:
echo "1234ABC89,234" | cut -d',' -f1
but it gives me 1234ABC89 which isn't what I want.
Assuming that you want to discard leading digits only, and that the letters will be all upper case, the following should work:
echo "1234ABC89,234" | sed 's/^[0-9]*\([A-Z]*\)\([0-9].*\)/\1\n\2/'
This works fine with GNU sed (I have 4.2.2), but other sed implementations might not like the \n, in which case you'll need to substitute something else.
Depending on the version of sed you can try:
echo "0520001DEF78,66" | sed -E -e 's/[0-9]*([A-Z]*)([,0-9]*)/\1\n\2/'
or:
echo "0520001DEF78,66" | sed -E -e 's/[0-9]*([A-Z]*)([,0-9]*)/\1$\2/' | tr '$' '\n'
DEF
78,66
Explanation: the regular expression replaces the input with the expected output, except instead of the new-line it puts a "$" sign, that we replace to a new-line with the tr command
Where do the strings come from? Are they read from a file (or other source external to the script), or are they stored in the script? If they're in the script, you should simply reformat the data so it is easier to manage. Therefore, it is sensible to assume they come from an external data source such as a file or being piped to the script.
You could simply feed the data through sed:
sed 's/^[0-9]*\([A-Z]*\)/\1 /' |
while read alpha number
do
…process the two fields…
done
The only trick to watch there is that if you set variables in the loop, they won't necessarily be visible to the script after the done. There are ways around that problem — some of which depend on which shell you use. This much is the same in any derivative of the Bourne shell.
You said you have many strings like this, so I recommend if possible save them to a file such as input.txt:
1234ABC89,234
0520001DEF78,66
46545455KRJ21,00
On your command line, try this sed command reading input.txt as file argument:
$ sed -E 's/([0-9]+)([[:alpha:]]{3})(.+)/\2\t\3/g' input.txt
ABC 89,234
DEF 78,66
KRJ 21,00
How it works
uses -E for extended regular expressions to save on typing, otherwise for example for grouping we would have to escape \(
uses grouping ( and ), searches three groups:
firstly digits, + specifies one-or-more of digits. Oddly using [0-9] results in an extra blank space above results, so use POSIX class [[:digit:]]
the next is to search for POSIX alphabetical characters, regardless if lowercase or uppercase, and {3} specifies to search for 3 of them
the last group searches for . meaning any character, + for one or more times
\2\t\3 then returns group 2 and group 3, with a tab separator
Thus you are able to extract two separate fields per line, just separated by tab, for easier manipulation later.
The target is always going to be between two characters, 'E' and '/' and there will never be but one occurrence of this combination, e.g. 'E01/' in most lines in the HTML file and will always be between '01' and '90'.
So, I need to programmatically read the file and replace each occurrence of 'Enn/' where 'nn' in 'Enn/' will be between '01' and '90' and must maintain the '0' for numbers '01' to '09' in 'Enn/' while incrementing the existing number by 1 throughout the HTML file.
Is this doable and if so how best to go about it?
Edit: Target lines will be in one or the other formats:
<DT>ProgramName
<DT>Program Name
You can use sed inside BASH as a fantastic one-liner, either:
sed -ri 's/(.*E)([0-9]{2})(\/.*)/printf "\1%02u\3" $((10#\2+(10#\2>=90?0:1)))/ge' FILENAME
or if you are guaranteed the number is lower than 100:
sed -ri 's/(.*E)([0-9]{2})(\/.*)/printf "\1%02u\3" $((10#\2+1)))/ge' FILENAME
Basically, you'll be doing inplace search and replace. The above will not add anything after 90 (since you didn't specify the exact nature of the overflow condition). So E89/ -> E90/, E90/ -> E90/, and if by chance you have E91/, it will remain E91/. Add this line inside a loop for multiple files
A small explanation of the above command:
-r states that you'll be using a regular expression
-i states to write back to the same file (be careful with overwriting!)
s/search/replace/ge this is the regex command you'll be using
s/ states you'll be using a string search
(.E) first grouping of all characters upto the first E (case sensitive)
([0-9]{2}) second grouping of numbers 0 through 9, repeated twice (fixed width)
(/.) third grouping getting the escaped trailing slash and everything after that
/ (slash separator) denotes end of search pattern and beginning of replacement pattern
printf "format" var this is the expression used for each replacement
\1 place first grouping found here
%02u the replace format for the var
\3 place third grouping found here
$((expression)) BASH arithmetic expression to use in printf format
10#\2 force second grouping as a base 10 number
+(10#\2>=90?0:1) add 0 or 1 to the second grouping based on if it is >= 90 (as used in first command)
+1 add 1 to the second grouping (see second command)
/ge flags for global replacement and the replace parameter will be an expression
GNU sed and awk are very powerful tools to do this sort of thing.
You can use the following perl one-liner to increment the numbers while maintaining the ones with leading 0s.
perl -pe 's/E\K([0-9]+)/sprintf "%02d", 1+$1/e' file
$ cat file
<DT>ProgramName
<DT>Program Name
<DT>Program Name
<DT>Program Name
$ perl -pe 's/E\K([0-9]+)/sprintf "%02d", 1+$1/e' file
<DT>ProgramName
<DT>Program Name
<DT>Program Name
<DT>Program Name
You can add the -i option to make changes in-place. I would recommend creating backup before doing so.
Not as elegant as one line sed!
Break the commands used into multiple commands and you can debug your bash or grep or sed.
# find the number
# use -o to grep to just return pattern
# use head -n1 for safety to just get 1 number
n=$(grep -o "E[0-9][0-9]\/" file.html |grep -o "[0-9][0-9]"|head -n1)
#octal 08 and 09 are problem so need to do this
n1=10#$n
echo Debug n1=$n1 n=$n
n2=n1
# bash arithmetic done inside (( ))
# as ever with bash bracketing whitespace is needed
(( n2++ ))
echo debug n2=$n2
# use sed with -i -e for inline edit to replace number
sed -ie "s/E$n\//E$(printf '%02d' $n2)\//" file.html
grep "E[0-9][0-9]" file.html
awk might be better. Maybe could do it in one awk command also.
The sed one-liner in other answer is awesome :-)
This works in bash or sh.
http://unixhelp.ed.ac.uk/CGI/man-cgi?grep