How to use variable with \ in sed command - bash

I have a strings file that I want to replace with sed command.
String in the file :
<ItemGroup>
<None Include="xyz.config" />
<BundleResource Include="Settings.txt" />
<BundleResource Include="Resources\Base.lproj\Localizable.strings" /> <BundleResource Include="Resources\Base.lproj\InfoPlist.strings" />
</ItemGroup>
Below is my command:
CONTENT="<BundleResource Include="Resources\Base.lproj\Localizable.strings" /><BundleResource Include="Resources\Base.lproj\InfoPlist.strings" /><BundleResource Include="Resources\es.lproj\Localizable.strings" /><BundleResource Include="Resources\es.lproj\Infoplist.strings" />"
sed -i "s|\.*<BundleResource Include=\"Resources\\Base.lproj\\.*|${CONTENT}|g"
but when I use above sed command, it didn't replace it. Do you have any idea why? Did I did wrong with my sed command?
My end result that I want is:
<ItemGroup>
<None Include="xyz.config" />
<BundleResource Include="Settings.txt" />
<BundleResource Include="Resources\Base.lproj\Localizable.strings" />
<BundleResource Include="Resources\Base.lproj\InfoPlist.strings" />
<BundleResource Include="Resources\es.lproj\Localizable.strings" />
<BundleResource Include="Resources\es.lproj\Infoplist.strings" />
</ItemGroup>
Thank you!

This is a problem with bash and sed backslash quoting. If you try
sed -i "s|\.*<BundleResource Include=\"Resources\\\\Base.lproj\\\\.*|${CONTENT}|g"
it should do what you want. Both bash and sed are collapsing backslahes, so you need four!
Also, you've used double quotes when assigning a string containing double quotes to CONTENT, which won't do what you want. Try single quotes:
CONTENT='<BundleResource Include="Resources\Base.lproj\Localizable.strings" /><BundleResource Include="Resources\Base.lproj\InfoPlist.strings" /><BundleResource Include="Resources\es.lproj\Localizable.strings" /><BundleResource Include="Resources\es.lproj\Infoplist.strings" />'
That said, as a commenter has pointed out, you'd be better off using a proper XML tool rather than sed.

As chepner mentioned, you should likely use an xml tool to edit this rather than SED, however, if you wanted to use SED, you likely want something like this:
sed "s|<BundleResource Include=\"Resources\\\\Base.lproj\\\\[^>]*>|${CONTENT}|g" < tmp.txt
I replaced the .*'s, as those can be problematic. The second .* is replaced with [^>]>, so it only matches/replaces until the next > character. I also double escaped the \ characters.

Related

bash/sed/awk: replace arbitrary substring in arbitrary files

Update:
I have tried markp-fuso's answer and it worked like a charm
I'm starting to get frustrated here as I'm not a daily user of bash/sed and the like.
Starting point:
I have many subfolders with many source files (.c,.cpp,.cxx).
These source files are referenced for compilation in project files (.vcxproj).
What I want to do:
I want to find all source files that contain the string #import. I then want to find all project files that reference those source files.
I then want to edit all occurences of these references inplace within these project files
e.g. <Include="folder/file.cpp"/> -> <Include="folder/file.cpp" Attribute="Value"/>
What I have tried:
egrep -lir --include=*.{c,cpp,cxx} "(#import)" ./e3 | xargs -L 1 basename | egrep -ir --include=*.vcxproj -f - ./e3 | sed 's/:/ /g'
which produces a list like that:
./src/base/base.vcxproj <ClCompile Include="Folder1\Folder1File1.cpp" />
./src/mod/mod.vcxproj <ClCompile Include="Folder2\Folder2File1.cpp" />
./src/ext/ext.vcxproj <ClCompile Include="Folder3\Folder3File1.cpp" />
So I then tried
egrep -lir --include=*.{c,cpp,cxx} "(#import)" ./e3 | xargs -L 1 basename | egrep -ir --include=*.vcxproj -f - ./e3 | sed 's/:/ /g' | awk '{ sed -iE 's/($2,$3)/\1 Attribute="Value"/g' }'
which errors out with
bash: syntax error near unexpected token `('
I have tried a solution with a shell script, which didn't work either and I don't know if and how to solve above error message. I'm open to any solution as long as it's running within bash, can even be more gross than what I came up with.
Setup:
mkdir -p src/{base,mod,ext}
echo 'some stuff on this line
<ClCompile Include="Folder1\Folder1File1.cpp" />
some more stuff on this line' > src/base/base.vcxproj
echo 'some stuff on this line
<ClCompile Include="Folder2\Folder2File1.cpp" />
some more stuff on this line' > src/mod/mod.vcxproj
echo 'some stuff on this line
<ClCompile Include="Folder3\Folder3File1.cpp" />
some more stuff on this line' > src/ext/ext.vcxproj
For the sake of getting something working in my environment I've placed the intermediate data in a local file:
$ cat proj.dat
./src/base/base.vcxproj <ClCompile Include="Folder1\Folder1File1.cpp" />
./src/mod/mod.vcxproj <ClCompile Include="Folder2\Folder2File1.cpp" />
./src/ext/ext.vcxproj <ClCompile Include="Folder3\Folder3File1.cpp" />
One idea using parameter substitution:
while read -r fname oldstring # 2nd-Nth space-delimited fields go into the single variable "oldstring"
do
oldstring="${oldstring//\\/\\\\}" # escape literal backslashes
newstring="${oldstring//\/>/ Attribute=\"Value\"\/>}" # replace /> with Attribute="Value"/>
echo "##################### ${fname}"
sed "s|${oldstring}|${newstring}|g" "${fname}"
done < proj.dat
NOTES:
sed replace applied to all occurences in a file
if additional datasets cause sed to abort with errors it may be necessary to add additional parameter expansions to escape other problematic characters
added a space on the front of the Attribute string since the textual description suggested a space may not exist before the /> (eg, ...file.cpp"/>)
OP should be able pipe the current egrep | xargs | egrep | sed to this while loop (replacing done < proj.dat with done)
once OP is satisfied with the results the -i flag can be added to the sed call to perform an inplace update of ${fname}
Generates:
##################### ./src/base/base.vcxproj
some stuff on this line
<ClCompile Include="Folder1\Folder1File1.cpp" Attribute="Value"/>
some more stuff on this line
##################### ./src/mod/mod.vcxproj
some stuff on this line
<ClCompile Include="Folder2\Folder2File1.cpp" Attribute="Value"/>
some more stuff on this line
##################### ./src/ext/ext.vcxproj
some stuff on this line
<ClCompile Include="Folder3\Folder3File1.cpp" Attribute="Value"/>
some more stuff on this line

Getting bad flag in substitute command '/' using sed replacement with custom string

I'm using macOS Sierra, and I'm and trying to manipulate a value of a key from a config file.
In order to achieve that I'm using (which works fine for simple values):
sed -i .bak "/^$KEY/s/\(.[^=]*\)\([ \t]*=[ \t]*\)\(.[^=]*\)/\1\2$VALUE/" $CONFIG_FILE
Unfortunately, my string $VALUE is quite complex with many special characters, giving me the error:
bad flag in substitute command: '/'
My $VALUE is being declared as:
VALUE='<Request xmlns="urn:oasis:names:tc:xacml:3.0:core:schema:wd-17" ReturnPolicyIdList="false" CombinedDecision="false"> <Attributes Category="urn:oasis:names:tc:xacml:3.0:attribute-category:resource"> <Attribute IncludeInResult="false" AttributeId="urn:oasis:names:tc:xacml:1.0:resource:resource-id"> <AttributeValue DataType="http://www.w3.org/2001/XMLSchema#string">test </AttributeValue> </Attribute> </Attributes> <Attributes Category="urn:oasis:names:tc:xacml:3.0:attribute-category:action"> <Attribute IncludeInResult="false" AttributeId="urn:oasis:names:tc:xacml:1.0:action:action-id"> <AttributeValue DataType="http://www.w3.org/2001/XMLSchema#string">testing something</AttributeValue> </Attribute> </Attributes> </Request>'
Since I have double quotes being part of the value of $VALUE, I can't use double quotes instead of single quote when declaring it... Any ideas to workaround this?
The problem is that $VALUE contains slashes which should be escaped because it conflicts with the separator for the substiture command
That's not convenient because if it changes, you have to escape them again. That's a solution, nevertheless.
Another simpler solution is to use an alternate separating character for s command which isn't in the $VALUE string, like % for instance (% has less chance to be in such a string, otherwise | can also be used).
sed -i .bak "/^$KEY/s%\(.[^=]*\)\([ \t]*=[ \t]*\)\(.[^=]*\)%\1\2$VALUE%" $CONFIG_FILE
or with pipe:
sed -i .bak "/^$KEY/s|\(.[^=]*\)\([ \t]*=[ \t]*\)\(.[^=]*\)|\1\2$VALUE|" $CONFIG_FILE

How to replace long one html-like string in files across multiple files in directories[sed,grep]?

I need to replace:
<m2: SplashScreen Image="Assets\SplashScreen.png"/></m2:VisualElements>
with this long string:
<m2:SplashScreen Image="Assets\SplashScreen.png" BackgroundColor="#ffffff" />
<m2:InitialRotationPreference>
<m2:Rotation Preference="landscape" />
</m2:InitialRotationPreference>
in many similar Package.appxmanifest file - not txt files. These file can be opened through texteditor
sed command did work, but only in one file.
sed -i '' 's/SplashScreen/xxxx/g' Package.appxmanifest
I want this replace done in all package.appxmanifest files across directories recursively.
attach to find
find yourpath -name Package.appxmanifest -exec sed -i ... {} \;
fill in the ... with your sed script.

Use sed to replace second pattern found

How can I use sed to replace second pattern found?
<default>...</default>
<default>...</default>
<default>...</default>
<setting id="lookandfeel.font" type="string" parent="lookandfeel.skin" label="13303" help="36107">
<level>1</level>
<default>Default</default>
</setting>
<default>...</default>
<default>...</default>
<default>...</default>
first pattern = lookandfeel.font
second pattern = Default
then replace "Default" with "Arial"
With GNU sed:
sed '/<setting id="lookandfeel.font"/,/<\/setting>/{s|<default>Default</default>|<default>Arial</default>|}' file
If you want to edit "in place" add option -i.
sed is for simple substitutions on individual lines, that is all. For anything even slightly more interesting you should be using awk for clarity, simplicity, robustness, portability, maintainability and almost every other desirable quality of software:
$ awk '/lookandfeel.font/{f=1} f&&sub(/Default/,"Arial"){f=0} 1' file
<default>...</default>
<default>...</default>
<default>...</default>
<setting id="lookandfeel.font" type="string" parent="lookandfeel.skin" label="13303" help="36107">
<level>1</level>
<default>Arial</default>
</setting>
<default>...</default>
<default>...</default>
<default>...</default>
Consider this regex in Perl
s/(lookandfeel\.skin[\S\s]*)Default/$1Arial/gm
see: DEMO
According to this question and this question which tell that sed is difficult to handle pattern in multiple lines then I suggest you to use perl instead of sed.
Note If you want more strict regex please add more information into your question.

Shellscript Read XML attribute value

We want to read XML attributes from an XML file. Example of file content is as below:
<properties>
<property name="abc" value="15"/>
<property name="xyz" value="26"/>
</properties>
We want to read value (i.e. 15) for property "abc" using shell script.
Please suggest shell commands to achieve this.
You can use a proper XML parser like xmllint. If your version supports xpath, it will be very easy to grab specific values. If it doesn't support xpath, then you can use --shell option like so:
$ echo 'cat //properties/property[#name="abc"]/#value' | xmllint --shell myxml
/ > -------
value="15"
/ >
You can then use awk or sed to format and extract desired field from output.
$ echo 'cat //properties/property[#name="abc"]/#value' | xmllint --shell myxmlfile | awk -F'[="]' '!/>/{print $(NF-1)}'
15
You can use command substitution to capture the output in a variable by saying:
$ myvar=$(echo 'cat //properties/property[#name="abc"]/#value' | xmllint --shell myxml | awk -F'[="]' '!/>/{print $(NF-1)}')
$ echo "$myvar"
15
Using anything else other than a xmlparser is prone to errors and will break easy.
quick and dirty
sed -n '/<Properties>/,\|</properties>| {
s/ *<property name="xyz" value="\([^"]*\)"\/>/\1/p
}'
no xml check and based on your sample so assume same structure (one property name per line, ...)
posix version (--posix for GNU sed)
sed -n '/<property name="abc"/s/.*value="\(.*\)"[^\n]*/\1/p' file
Creates a hold pattern for the value then matches everything except for the newline to avoid printing the newline, it expects the value double quoted as per your example data.
E.g.
<properties>
<property name="abc" value="15"/>
<property name="xyz" value="26"/>
</properties>
Output:
15
(Prior to edit: sed '/<property name="abc"/s/.*value="\(.*\)"[^\n]*/\1/' file)

Resources