sed not working with variables - regex

I am trying to compile a large project that I downloaded from Internet and I have a custom script that runs configure, make and make install. The problem is that I want to pass some compatibility flags but the project uses libtool which doesn't recognize LDFLAGS and I have to find another way to pass my compatibility flags. So in my script after running configure I have code that looks like this:
COMPATFLAG="-gcc-name=/usr/local/gcc-4.2.1/bin/gcc"
sed -i "s/CC -shared/CC -shared ${COMPATFLAG}/g" libtool
${COMPATFLAG} may get different values depending on what platform is being compiled for so I cannot put it in clear text in the sed statement. The problem is that it doesn't work - I get an empty space instead of the value of ${COMPATFLAG}. If I instead write sed -i "s/CC -shared/CC -shared TEST/g" libtool it adds the string "TEST" to the file. If I use single quotes I get the ${COMPATFLAG} string and not the variable's value. If I try to export ${COMPATFLAG} it still doesn't work. I don't know why it goes wrong. Any idea?

Since COMPATFLAG does have forward slashes you should use a different regex delimiter in sed:
sed -i "s#CC -shared#& ${COMPATFLAG}#g" libtool
Also I have used # here in order to avoid repetition of matched string in replacement pattern.
Thanks to #JonathanLeffler for this tip, you can even use a control character as regex delimiter:
sed -i "s^GCC -shared^G& ${COMPATFLAG}^Gg" libtool
Type ^G as ctrl-V-G in shell

You can use pipe | instead of / with a double quote. That works for the global variables that include /.
sed -e "s|CC -shared|CC -shared ${COMPATFLAG}|g" libtool

Related

Find and replace pattern in large number of files

I want to replace text in about 80.000 log files using a regex. I love the batch search and replace of VSCode. I was unable to do this with VSCode, because it did not seem to handle this amount of data well. Any suggestion how I could do this with VSCode? Are there suggestions for alternatives?
Instead of depending on a GUI based tool, it might be easier to for a CLI tool for this.
If you're using Linux, or willing to install any of the tools like sed and find if you're on Windows then it should be relatively simple.
You can use sed which is a command line tool on all (or at least most) distributions of Linux, and can be installed on Windows.
Usage (for this use case):
sed -i s/{pattern}/{replacement}/g {file}
Use sed to replace the matched pattern with a replacement, using the global modifier to match all results, and the file to do the replacement and overwrite.
To target all files in a directory you can do:
find -type f -name "*.log" exec sed -i s/{pattern}/{replacement}/g {};
Find items recursively starting from the current directory where it's type is file, and it has a name ending with .log. Then use sed to replace the pattern with the contents you want for each matched file.
You can find how to get tools like sed and find for Windows on the following question:
https://stackoverflow.com/a/127567/6277798

Why does `perl -i -p0e <expression>` work, but not `perl -0 -pie <expression>`?

If I try perl -pie 's/foo/bar/' file.txt it works as expected: the find-replace expression is executed, and the result is saved to the original file.
However, if I want to use the -0 to run an expression that includes newlines, simply prepending the option doesn't work:
$ perl -0 -pie 's/foo\nbar/qux/' file.txt
Can't open perl script "s/foo\nbar/qux/": No such file or directory
After several attempts, the following combination worked:
$ perl -i -p0e 's/foo\nbar/qux/' file.txt
My question is: why does the first order of options produce an error (especially when plain -pie works as expected), while the second ordering is correctly handled?
-i means work in-place without backup.
-ie means work in-place, with backup. The backup has the same name as the original file, but with e appended.
That means that perl -pie 's/foo/bar/' file.txt didn't work either (unless you have a Perl file named s/foo/bar/).
If you simply arrange the options logically, you avoid the problem. -i has nothing to do with the program —it'll still work if added/removed— so it makes more sense to place it first anyway. -p and -0777, otoh, are part of the program, so it makes sense to place them next to -e. So writing the command sensibly results in one of the following:
perl -i -0777pe'...' ...
perl -i~ -0777pe'...' ...
perl -0777pe'...' ...
Note that I used -0777, since -0 treats the input as NUL-terminated lines rather than activating slurp mode.

How to scrub emails from all CSVs in a directory?

I have this regex that works fine enough for my purposes for identifying emails in CSVs within a directory using grep on Mac OS X:
grep --no-filename -E -o "\b[a-zA-Z0-9.-]+#[a-zA-Z0-9.-]+\.[a-zA-Z0-9.-]+\b" *
I've tried to get this working with sed so that I can replace the emails with foo#bar.baz:
sed -E -i '' -- 's/\b[a-zA-Z0-9.-]+#[a-zA-Z0-9.-]+\.[a-zA-Z0-9.-]+\b/foo#bar.baz/g' *
However, I can't seem to get it to work. Admittedly, sed and regex are not my strong points. Any ideas?
The sed in OSX is broken. Replace it with GNU sed using Homebrew that will be used as a replacement for the one bundled in OSX. Use this command for installation
sudo brew install gnu-sed
and use this for substitution
sed -E -i 's/\b[a-zA-Z0-9.-]+#[a-zA-Z0-9.-]+\.[a-zA-Z0-9.-]+\b/foo#bar.baz/g' *
Reference
You seem to assume that grep and sed support the same regex dialect, but that is not necessarily, or even usually, the case.
If you want a portable solution, you could easily use Perl for this, which however supports yet another regex dialect...
perl -i -p -e 's/\b[a-zA-Z0-9.-]+#[a-zA-Z0-9.-]+\.[a-zA-Z0-9.-]+\b/foo#bar.baz/g' *
For a bit of an overview of regex dialects, see https://stackoverflow.com/a/11857890/874188
Your regex kind of sucks, but I understand that is sort of beside the point here.

Sed command - order of option flags matters? (-ir vs -ri)

Imagine the following data stored in file data.txt
1, StringString, AnotherString 545
I want to replace "StringString" with "Strung" with the following code
sed -ir 's/String+/Strung/g' data.txt
But it won't work. This works though:
sed -ri 's/String+/Strung/g' data.txt
I don't see any reason why the order of option flags would matter. Is it a bug or is there an explanation?
Please note that I'm not looking for a workaround but rather why the order of -ir and -ri matters.
Sidenotes: The switch -i "edits the file in place" while -r allows "extended regular expression" (allowing the + operator). I'm running sed 4.2.1 Dec. 2010 on Ubuntu 12.10.
When doing -ir you are specifying that "r" should be the suffix for the backup file.
You should be able to do -i -r if you need them in that order
Did you check sed --help or man sed?
-i[SUFFIX], --in-place[=SUFFIX]
edit files in place (makes backup if extension supplied).
The default operation mode is to break symbolic and hard links.
This can be changed with --follow-symlinks and --copy.

Copy and Rename Multiple Files with Regular Expressions in bash

I've got a file structure that looks like:
A/
2098765.1ext
2098765.2ext
2098765.3ext
2098765.4ext
12345.1ext
12345.2ext
12345.3ext
12345.4ext
B/
2056789.1ext
2056789.2ext
2056789.3ext
2056789.4ext
54321.1ext
54321.2ext
54321.3ext
54321.4ext
I need to rename all the files that begin with 20 to start with 10; i.e., I need to rename B/2022222.1ext to B/1022222.1ext
I've seen many of the other questions regarding renaming multiple files, but couldn't seem to make it work for my case. Just to see if I can figure out what I'm doing before I actually try to do the copy/renaming I've done:
for file in "*/20?????.*"; do
echo "{$file/20/10}";
done
but all I get is
{*/20?????.*/20/10}
Can someone show me how to do this?
You just have a little bit of incorrect syntax is all:
for file in */20?????.*; do mv $file ${file/20/10}; done
Remove quotes from the argument to in. Otherwise, the filename expansion does not occur.
The $ in the substitution should go before the bracket
Here is a solution which use the find command:
find . -name '20*' | while read oldname; do echo mv "$oldname" "${oldname/20/10}"; done
This command does not actually do your bidding, it only prints out what should be done. Review the output and if you are happy, remove the echo command and run it for real.
Just wanna add to Explosion Pill's answer.
On OS X though, you must say
mv "${file}" "${file_expression}"
Or the mv command does not recognize it.
Brace expansions like :
{*/20?????.*/20/10}
can't be surrounded by quotes.
Instead, try doing (with Perl rename) :
rename 's/^10/^20/' */*.ext
You can do this using the Perl tool rename from the shell prompt. (There are other tools with the same name which may or may not be able to do this, so be careful.)
If you want to do a dry run to make sure you don't clobber any files, add the -n switch to the command.
note
If you run the following command (linux)
$ file $(readlink -f $(type -p rename))
and you have a result like
.../rename: Perl script, ASCII text executable
then this seems to be the right tool =)
This seems to be the default rename command on Ubuntu.
To make it the default on Debian and derivative like Ubuntu :
sudo update-alternatives --set rename /path/to/rename
The glob behavior of * is suppressed in double quotes. Try:
for file in */20?????.*; do
echo "${file/20/10}";
done