0

I'm trying to create an alias function to remove inputted line from file:

function remove_line(){
       line_to_remove="'s/^"$1"$//g'"
       sed -i $(line_to_remove) my_file
}

For example: remove_line domain.com should remove this domain from a given file.

However it seems that the $ is not interpreted correctly. What am I doing wrong?

2
  • 2
    Don't use function foo(). It mixes ksh-style and POSIX style function definitions and only works in some shells without having any advantage over the POSIX foo() function definitions. (Particularly because it does not work in ksh, where the function keyword would make a difference.)
    – ilkkachu
    Commented Apr 23, 2024 at 9:21
  • 1
    Count your quotes. "'s/^"$1"$//g'" is "'s/^" $1 "$//g'" which is 2 double-quoted strings with $1 in the middle exposed to the shell. You probably meant "'s/^$1$//g'" so that $1 would be quoted but even then the 's are no longer semantic, they're just literal characters in a string, and then when you do $(line_to_remove) you're exposing the whole thing to the shell for globbing, word splitting and filename expansion. Never put any part of any script in a scalar variable though as it'll always fail for the non-trivial cases, see mywiki.wooledge.org/BashFAQ/050.
    – Ed Morton
    Commented Apr 23, 2024 at 11:37

1 Answer 1

2

Your main issue is that $(line_to_remove) is a command substitution, i.e., an expression replaced by the text outputted by the command inside the parentheses (line_to_remove, which I would expect give you a "command not found" error). You are probably wanting to use "$line_to_remove" instead.

However, injecting a shell variable into a sed substitution expression is less than ideal as it puts too many conditions on the injected string (it has to be a valid regular expression and can't contain /, etc.)

Instead, I suggest using grep and either writing the result to the standard output or to a temporary file that replaces the original file upon successful execution of grep, or you can let sponge (from the GNU moreutils package) overwrite the original file.

remove_lines () {
       grep -v -Fx -e "$1" my_file | sponge my_file
}

The options used with grep here ensure that the line given as the function's first argument is used as-is (not as a regular expression), matches a line from start to end, and removes matching lines from the input.

Without sponge:

remove_lines () {
       tmpfile=$(mktemp)
       grep -v -Fx -e "$1" my_file >"$tmpfile" && mv "$tmpfile" my_file
       rm -f "$tmpfile"
}

The mv is performed only if the grep is successfully executed, and the rm -f removes the temporary file in the case that it didn't.


Personally, I would probably write the function like this:

remove_lines () {
       grep -v -Fx -e "$1"
}

This allows a user to use it like so:

remove_lines "my line" <my_file | sponge my_file

some-command | remove_lines "bumble bee" | other-command

This makes the function work as a filter that only does what it says on the tin, i.e., it removes lines from the input and does not care what where the input is coming from or where it's going.

3
  • 1
    or ${line_to_remove}
    – X Tian
    Commented Apr 23, 2024 at 8:52
  • 2
    @XTian Well, "${line_to_remove}" in that case, but the curly braces are not needed at all here and provide no benefit.
    – Kusalananda
    Commented Apr 23, 2024 at 8:53
  • tmpfile=$(mktemp) should really be local tmpfile=$(mktemp) || return or similar so it doesn't overwrite a tmpfile variable in calling code and doesn't try to write to the temp file if it failed to create the temp file. mktemp and cd are 2 commands that should always have a || return/exit after them.
    – Ed Morton
    Commented Apr 23, 2024 at 11:41

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.