0

I am trying to write a Bash script handling Pacman repositories in /etc/pacman.conf usable within a script without depending on the user interaction required by a text editor.

I am having troubles figuring out the remove-option. How do I delete multiple lines or a paragraph from a file based on the content of a multiline variable?

So I have this multiline variable

echo "$repo_query_result"

and its content looks for example like this

[repo-name]
Server = https://host.domain/$arch

or for example like this for a disabled repo

#[repo-testing]
#SigLevel = PackageRequired
#Include = ./repo-2-mirrorlist

When I try the following command which works with single-line variables

sed "/$repo_query_result/d" /etc/pacman.conf

I get the following error

sed: -e expression #1, char 6: unterminated address regex

All I could find regarding sed or awk and multiline variables where solutions on how to delete a single line from the variable. Or when it comes to deleting paragraphs it was always a fixed pattern, e.g. the same start and end word, which is not suitable for my case.

EDIT1: The variable is the result of a function within the script and not set manually or derived from another file. So altering the variable is not an option

repo_query()
{
sed "/$name/,/+$/!d" $conff | sed '/^$/,/+$/d'
}
repo_query_result="$(repo_query | cat /dev/stdin)"
tripleee
  • 175,061
  • 34
  • 275
  • 318
  • `sed` processes the file one line at a time. It can't match a multi-line regexp. – Barmar May 18 '23 at 20:19
  • ... but if you're using GNU `sed`, you can change what it considers a "line". The `-z` option will make it expect lines to be separated by null characters instead of newlines. If the input is a text file then this usually makes it consider the whole file as one line. You would not then want to execute a `d` command on that line, but you could remove matches by `s`ubstituting empty strings for them. – John Bollinger May 18 '23 at 20:25
  • The variable as input is fixed unfortunately, see EDIT1. – herrfleischladen May 18 '23 at 20:44
  • Better answer is "don't". This is why modern systems support configuration _directories_, not just configuration _files_, so when you add an extra config segment you put it in its own file and you can remove it be just deleting that file. – Charles Duffy May 19 '23 at 15:19
  • In particular, pacman supports an `Include` directive, and its targets can include wildcards; so one can either have a one-line include directive pointing to an individual file that's trivially deleted (both the include directive and the file being easier to delete on their own), _or_ an include directive with a glob pointing at a directory of files, any entry in that directory being deleted with just a `rm`. – Charles Duffy May 19 '23 at 15:20
  • I know `Include`, it is even in my examples. But you still have to edit `pacman.conf` to use `Include`. And currently, at least to my knowledge, there is no convenient way to manage repositories without a text editor that requires user interaction and cannot be use within a script. Furthermore, if you just delete file `Include` is pointing to, you get errors because the repository is still in the configuration file without a location (server or mirrorlist). – herrfleischladen May 19 '23 at 16:25
  • Please don't add "solved" or make similar edits. Your question should remain strictly a question; the way to mark it as resolved is to accept an answer, which you have already done. – tripleee May 19 '23 at 17:49

3 Answers3

0

Would something like this work?

#!/bin/bash

# Function to retrieve the content to be removed
repo_query() {
    sed "/$name/,/+$/!d" "$conff" | sed '/^$/,/+$/d'
}

# Set the name and conff variables as per your requirements
name="repo-1-name"
conff="/etc/pacman.conf"

# Call the function to get the content to be removed
repo_query_result=$(repo_query)

# Create a temporary file
tmp_file=$(mktemp)

# Copy the original file to the temporary file, excluding the lines to be removed
sed "/$repo_query_result/Id" "$conff" > "$tmp_file"

# Overwrite the original file with the temporary file
mv "$tmp_file" "$conff"

# Remove the temporary file
rm "$tmp_file"
Robert
  • 123
  • 4
  • error ´sed: -e expression #1, char 6: unterminated address regex rm: cannot remove '/tmp/tmp.UX1Yphcuxp': No such file or directory´ and the resulting conff is empty – herrfleischladen May 18 '23 at 21:01
  • @herrfleischladen Change the repo query to awk? repo_query() { awk -v name="$name" ' $0 ~ "[" name "]" { printing = 1 next } printing && /^\[/ { exit } printing { print } ' "$conff" } – Robert May 18 '23 at 21:15
  • Yours gives me a syntax error. `awk "/$name/,/^$/" $conff | awk "!/^$/"` produces the same result as `sed "/$name/,/+$/!d" $conff | sed "/^$/,/+$/d"` – herrfleischladen May 18 '23 at 21:56
0
awk -v r="$repo_query_result" '
    BEGIN { n=split(r,a,/\n/); s=a[1] }
    s && $0==s { s=0; d=n }
    --d<0
' in.conf >out.conf
  • pass lines to remove as variable r
  • split r on newlines, store in array a (there are n parts)
  • a[1] is first line to remove
  • initialise s to a[1]
  • if line exactly matches a[1]:
    • clear s so we don't look for any more copies
    • set counter d to n (number of lines to remove)
  • d starts out as 0 so decrementing will give negative value (until d is set)
    • when true, default action (print) happens
jhnc
  • 11,310
  • 1
  • 9
  • 26
  • might also work: `awk -v r="$repo_query_result" -v RS= 'n=index($0,r) { $0 = substr($0,1,n-1) substr($0,n+1+length(r)) } 1' in.conf >out.conf` – jhnc May 18 '23 at 22:28
  • `awk -v r="$repo_query_result" -v RS= 'n=index($0,r) { $0 = substr($0,1,n-1) substr($0,n+1+length(r)) } 1' in.conf >out.conf` works too but also deletes every empty line in the rest of the file. – herrfleischladen May 18 '23 at 22:42
  • oops. I only tested on very simple data. better not use that version then. Probably should have been `RS='\0'` – jhnc May 18 '23 at 22:46
0

Pure bash, using pattern substitution:

conf=$(<"in.cf")
echo "${conf/"$repo_query_result"}" >"out.cf"
jhnc
  • 11,310
  • 1
  • 9
  • 26
  • 1
    Like this it doesn't seem to do anything, the conf-file stays the same. But if I leave out `$'\n'` it works. ```conf=$(<"in.cf")``` ```echo "${conf/"$repo_query_result"}" >"out.cf"``` – herrfleischladen May 19 '23 at 11:55
  • usually, assignments to variables in bash strip the trailing newline. If your variable keeps it, then yes, even simpler to leave out the newline – jhnc May 19 '23 at 13:43
  • this variant will minimise trailing newline changes: `mapfile -d '' conf <"in.cf"; printf '%s' "${conf/"$repo_query_result"}" >"out.cf"` – jhnc Jun 19 '23 at 02:47