-1

I would like to remove the double slashes at the beginning of a line to uncomment a line of code, the first commented for example. My approach works fine with a lot of other cases, but this time it isn't sufficient. Here is a piece of code from my file .sh:

while read line
    do
        if [[ $line == *"#define asdasd"* ]]
        then
            local prefix="\\/\\/ "
            local new=${line#$prefix}
            local sub="s/$line/$new/g"
            echo "old line: " $line
            echo "new line: " $new
            echo $sub
            perl -pi -e "$sub" $FILE
            exit
           fi
    done < $FILE

EDIT:

My problem is more complex than this code. I want to uncomment (remove the double slashes) only one line of the lines that match the pattern and not necessarily the first one. It is the reason why I can't operate on a whole file, but line by line. So all the "perl on match" solutions doesn't work in my case.

EDIT:

I'm editing my question as suggested by some users.

  • Background: I'm working with an embedded (complex) OS and I can't modify every files for my own ends (i.e. the makefile).
  • Purpose: I would like to build the code several times and every time with a different flag enabled.

In my current solution, I collect all the flags in a header file and I enable only one by one at every build. Here is an example of my .h file:

#ifndef SYSTEM_H
#define SYSTEM_H

#include <...>

#define BLA       1
#define BLABLA    2
#define BLABLABLA 3

// #define OPT_0 // Options disabled
// #define OPT_1
#define OPT_2 // Option enabled
// #define OPT_...
// #define OPT_...
// #define OPT_N

[...]

#define function(__exp) \
  [...]

#endif

As you can see, I don't know how many options I have to enable/disable and there are other #define instructions.

I'm writing a script that enable one flag at a time and build the project. Every time the compiled file is renamed to prevent the overwriting. A piece of my solution is based on the code listed above, but unfortunately the perl instruction doesn't modify the .h file removing the double slashes at the beginning of the selected line.

Zeb
  • 1,715
  • 22
  • 34
  • 4
    if you paste in shellcheck.net you see an error: "Make sure not to read and write the same file in the same pipeline.". That is, you are looping a file and editing it in the meanwhile. Also, it looks to me that you are choosing a long approach: wouldn't it be better to just do a `perl -pi` to the file, without looping through it? – fedorqui Apr 21 '15 at 15:41
  • My problem is a lot more complex. So trust me, I have to read through my file and exam every single line. – Zeb Apr 21 '15 at 16:04
  • 2
    OK then you must have into consideration that you cannot change what you are reading, because the behaviour will be unpredictable. – fedorqui Apr 21 '15 at 16:11
  • Ok, I understand that it isn't the most safe approach, but till now it works well. I will solve this problem in a second moment, promise! Anyway, I didn't know shellcheck.net, so thank you;-) – Zeb Apr 21 '15 at 16:18
  • 1
    Your edit doesn't make it any clearer what you're trying to do and your current approach is inherently broken. I strongly advise you to edit to clarify, otherwise I'm afraid that no-one is going to be able to properly help you. – Tom Fenech Apr 21 '15 at 16:29
  • Can you describe what the specific problem is? The reading from and writing to the same file in the loop has been pointed out, but `sub="s/$line/$new/g"` is also likely to give you trouble, because values with unescaped special regex characters / special replacement-string characters will make the `perl` command either not work as intended or break altogether. – mklement0 Apr 21 '15 at 17:08
  • Question updated. I hope it will be more exhaustive. Thanks again. – Zeb Apr 22 '15 at 11:03
  • @Zeb: Unfortunately, by completely rewriting your question you've now invalidated existing answers. I suggest restoring the original question and _appending_ the new information. As for the new information: you've now described what you _want_ to do, but not what you need help with, specifically. – mklement0 Apr 22 '15 at 13:32
  • I edited my question as you suggested. However, my former question concerned a string substitution that does not work, no my whole problem. I don't know why such an interesting on my algorithmic solution, my question was quite specific. – Zeb Apr 22 '15 at 13:57
  • 1
    @Zeb: See my updated answer. As for the question being specific: only in the most recent update do you explicitly mention that you have trouble with the _Perl_ command - and to help you with that, we obviously need to see it and the surrounding code; while all that was in your original question, it lacked a specific problem description. – mklement0 Apr 22 '15 at 14:14
  • @Zeb: I've provided another update, which adds functions for robust escaping of your strings for use in `perl`'s `s///` function. – mklement0 Apr 22 '15 at 17:04

3 Answers3

3

Fedorqui's comment is right on point but you're making a real meal out of this!

To edit the line you're interested in, just use Perl:

perl -pi -e 's/^\/\/// if /#define asdasd/' "$FILE"

This reads through your file and makes the substitution to remove the slashes from the start of the line matching the pattern /#define asdasd/.

It's no shorter in this case but you could also combine the two patterns, like this:

perl -pi -e 's/^\/\/(.*#define asdasd)/\1/' "$FILE"

It's unclear whether it was a requirement or not to only perform one substitution but if it was, you could change the command to this:

perl -pi -e 's/^\/\/// if /#define asdasd/ && !$n++' "$FILE"

!$n++ is only true the first time it is evaluated, so only one substitution will be carried out.

Community
  • 1
  • 1
Tom Fenech
  • 72,334
  • 12
  • 107
  • 141
  • As I wrote in a comment above, my problem is more complex than this code. I want to uncomment (remove the double slashes) only one line of the lines that match the pattern and not necessarily the first one. It is the reason why I can't operate on a whole file, but line by line. – Zeb Apr 21 '15 at 16:13
  • 2
    In that case, you need to edit your question to make it clearer exactly what the condition is for substitution. We cannot come up with a solution based on your current method without knowing _why_ it has to be like that. I can't imagine a situation where calling perl within a `while read` loop is going to be a good idea, especially on the same file. – Tom Fenech Apr 21 '15 at 16:19
  • Just done! First of all thanks for your help. My question was quite specific, I was looking for a solution for my substitution problem, not for all my algorithmic approach. Next time I will explain all my problem. – Zeb Apr 21 '15 at 16:24
1

If you really need to process the file line by line, here's how to fix the problem of reading from and writing to the same file in the loop, pointed out by @fedorqui in a comment (code uses bash syntax):

while IFS= read -r line; do
  if [[ $line == *"#define asdasd"* ]]
  then
    # ... process "$line" and update "$FILE"
  fi
done <<<"$(< "$FILE")"
  • "$(< $FILE)" reads the entire file up front, and then uses a here-string to pipe it to the while loop's stdin.
    • Since the entire file is read at once, this approach is not advisable for huge input files.
    • On a side note: better not to use all-uppercase variable names such as $FILE to prevent clashes with environment / special shell variables.
  • Also note that I've made the read command more robust: IFS= means that leading and trailing whitespace is preserved (which you may or may not want), and -r ensures that \ instances aren't "eaten" by read.

As for the code inside your loop:

  • new=${line#$prefix} does not strip // from the beginning of $line, because you've escaped the / chars. assigned to $prefix - use prefix='//' instead. Note that any unescaped / in either $line or $new invariably breaks your perl command - other chars. can cause problems, too: see next point.

  • sub="s/$line/$new/g" is a fragile way of constructing a replacement command, because if $line and/or $new happen to contain characters with special meaning in the context of a regular expression and a replacement string or even just the delimiter string, /, the command will either misbehave or break.

  • You can perform safe escaping of your strings for use in perl's s/// with the following bash functions (which are explained here]:
# SYNOPSIS
#   quotePerlRe <text>
quotePerlRe() { sed -e 's/[^^\\$]/[&]/g; s/\^/\\^/g; s/\\/\\\\/g; s/\$/\\$/g; $!a\'$'\n''\\n' <<<"$1" | tr -d '\n'; }
# SYNOPSIS
#  quotePerlSubst <text>
quotePerlSubst() {
  IFS= read -d '' -r < <(sed -e ':a' -e '$!{N;ba' -e '}' -e 's/[$/\]/\\&/g; s/\n/\\&/g' <<<"$1")
  printf %s "${REPLY%$'\n'}"
}

You would then call them as follows:

local sub="s/$(quotePerlRe "$line")/$(quotePerlSubst "$new")/g"
Community
  • 1
  • 1
mklement0
  • 382,024
  • 64
  • 607
  • 775
0

Could you use sed for this like

sed -i -e '/#define asdasd/ s,^//,,' $FILE

So you'll only match the pattern you want, then remove the double slash.

Eric Renouf
  • 13,950
  • 3
  • 45
  • 67