Reputation: 1642
I have a file "input.txt" that contains the following at Line Number 18:
entry({"servicectl"}, alias("servicectl", "status")).sysauth = "Admin"
I want to replace "Admin" with {"Admin", "root"}
---> "root" is just an example name, It can be anything.
So after the replacement the Line Number 18 should become
entry({"servicectl"}, alias("servicectl", "status")).sysauth = {"Admin", "root"}
Another run of the sed command with different input name: "user1"
entry({"servicectl"}, alias("servicectl", "status")).sysauth = {"Admin", "root", "user1"}
And so on, there can be multiple entries into this Line Number 18 of File input.txt
...
I would like to remove it as well.
That is if the file "input.txt" contains following at Line Number: 18
entry({"servicectl"}, alias("servicectl", "status")).sysauth = {"Admin", "root", "user1"}
Then upon removal of "root" it should become :
entry({"servicectl"}, alias("servicectl", "status")).sysauth = {"Admin", "user1"}
Upon Removal of "user1" it should become:
entry({"servicectl"}, alias("servicectl", "status")).sysauth = "Admin"
Upvotes: 2
Views: 322
Reputation: 203254
It's still not entirely clear but this may be what you want:
$ cat tst.awk
BEGIN {
FS = "[[:space:]]*=[[:space:]]*"; OFS = " = "
sfs = "[[:space:]]*,[[:space:]]*"; sofs = ", "
split(cmd,tmp)
act = who = cmd
sub(/=.*/,"",act)
sub(/[^=]+=/,"",who)
who = "\"" who "\""
}
{ gsub(/[{}]/,"",$2) }
act == "add" { $2 = $2 sofs who }
act == "del" {
split($2,subflds,sfs)
$2 = ""
for (i=1; i in subflds; i++) {
if (subflds[i] != who) {
$2 = ($2 ? $2 sofs : "") subflds[i]
}
}
}
$2 ~ sofs { $2 = "{" $2 "}" }
{ print }
.
$ cat file
entry({"servicectl"}, alias("servicectl", "status")).sysauth = "Admin"
entry({"servicectl"}, alias("servicectl", "status")).sysauth = {"Admin", "root"}
entry({"servicectl"}, alias("servicectl", "status")).sysauth = {"Admin", "root", "user1"}
$ awk -v cmd='add=user2' -f tst.awk file
entry({"servicectl"}, alias("servicectl", "status")).sysauth = {"Admin", "user2"}
entry({"servicectl"}, alias("servicectl", "status")).sysauth = {"Admin", "root", "user2"}
entry({"servicectl"}, alias("servicectl", "status")).sysauth = {"Admin", "root", "user1", "user2"}
$ awk -v cmd='add=some name or other' -f tst.awk file
entry({"servicectl"}, alias("servicectl", "status")).sysauth = {"Admin", "some name or other"}
entry({"servicectl"}, alias("servicectl", "status")).sysauth = {"Admin", "root", "some name or other"}
entry({"servicectl"}, alias("servicectl", "status")).sysauth = {"Admin", "root", "user1", "some name or other"}
$ awk -v cmd='del=user1' -f tst.awk file
entry({"servicectl"}, alias("servicectl", "status")).sysauth = "Admin"
entry({"servicectl"}, alias("servicectl", "status")).sysauth = {"Admin", "root"}
entry({"servicectl"}, alias("servicectl", "status")).sysauth = {"Admin", "root"}
$ awk -v cmd='del=root' -f tst.awk file
entry({"servicectl"}, alias("servicectl", "status")).sysauth = "Admin"
entry({"servicectl"}, alias("servicectl", "status")).sysauth = "Admin"
entry({"servicectl"}, alias("servicectl", "status")).sysauth = {"Admin", "user1"}
You don't say what you want done if there's no users left in the list after a deletion:
$ awk -v cmd='del=Admin' -f tst.awk file
entry({"servicectl"}, alias("servicectl", "status")).sysauth =
entry({"servicectl"}, alias("servicectl", "status")).sysauth = "root"
entry({"servicectl"}, alias("servicectl", "status")).sysauth = {"root", "user1"}
or if you try to add an existing user:
$ awk -v cmd='add=Admin' -f tst.awk file
entry({"servicectl"}, alias("servicectl", "status")).sysauth = {"Admin", "Admin"}
entry({"servicectl"}, alias("servicectl", "status")).sysauth = {"Admin", "root", "Admin"}
entry({"servicectl"}, alias("servicectl", "status")).sysauth = {"Admin", "root", "user1", "Admin"}
or if you ask to delete a user that doesn't exist:
$ awk -v cmd='del=stranger' -f tst.awk file
entry({"servicectl"}, alias("servicectl", "status")).sysauth = "Admin"
entry({"servicectl"}, alias("servicectl", "status")).sysauth = {"Admin", "root"}
entry({"servicectl"}, alias("servicectl", "status")).sysauth = {"Admin", "root", "user1"}
Or I expect many other scenarios I haven't thought of but whatever it is you need to do is all just trivial tweaks to handle - you just need to think of these and any other rainy-day scenarios you might have to deal with and include the input/output in your question to clarify your requirements.
BTW any solution you get (sed especially) I would test with these unusual user names:
$ awk -v cmd='add=a'"'"'\\tweird&=set\\1/"of chars"' -f tst.awk file
entry({"servicectl"}, alias("servicectl", "status")).sysauth = {"Admin", "a'\tweird&=set\1/"of chars""}
entry({"servicectl"}, alias("servicectl", "status")).sysauth = {"Admin", "root", "a'\tweird&=set\1/"of chars""}
entry({"servicectl"}, alias("servicectl", "status")).sysauth = {"Admin", "root", "user1", "a'\tweird&=set\1/"of chars""}
$ awk -v cmd='del=A.*' -f tst.awk file
entry({"servicectl"}, alias("servicectl", "status")).sysauth = "Admin"
entry({"servicectl"}, alias("servicectl", "status")).sysauth = {"Admin", "root"}
entry({"servicectl"}, alias("servicectl", "status")).sysauth = {"Admin", "root", "user1"}
because it's extremely likely you'll get highly undesirable results. Awk handles it just fine because awk is operating on literal strings but sed cannot operate on strings and so has to deal with regexp metacharacters plus it's own delimiters plus backreferences, etc. and, typically, it can't.
Upvotes: 0
Reputation: 6272
If i understand correctly what do you want: start from this input file:
# file input.txt
# line 1
# line ...
# line 17
entry({"servicectl"}, alias("servicectl", "status")).sysauth = "Admin"
Using this sed instruction (with relaxed space tolerance):
ENTRY='new_user' && sed '18s/=\s*{\?\s*\(".*"\)\s*}\?\s*/= {\1, "'"${ENTRY}"'"}/' input.txt > output.txt
To obtain this:
# file output.txt
# line 1
# line ...
# line 17
entry({"servicectl"}, alias("servicectl", "status")).sysauth = {"Admin", "new_user"}
As you request you can apply the sed command multiple times to append new entries to the 18th line.
Then you can remove the last entry one by one (till eventually return to the original input.txt) with this:
sed '18s/=\s*{\s*\("[^"]*"\)\s*,\s*\("[^"]*"\)\s*}/= \1/; 18s/=\s*{\s*\("[^"]*"\)\(\(\s*,\s*"[^"]*"\)\{1,\}\)\s*,\s*"[^"]*"\s*}/= { \1\2 }/' output.txt
To remove entries i considered two cases: when there is only one ${ENTRY}
(to know when remove curly braces) and when there are 2 or more ${ENTRY}
repeated.
UPDATE this version, similarly to the adduser version accept a shell variable to remove a specific user:
ENTRY='user3' && sed '18s/=\s*{\s*\("[^"]*"\)\s*,\s*\("'"${ENTRY}"'"\)\s*}/= \1/; 18s/=\s*{\s*\("[^"]*"\)\(.*\)\(\s*,\s*"'"${ENTRY}"'"\)\(.*\)\s*}/= { \1\2\4 }/' output.txt
It works, only note that i've considered Admin untouchable.
NB note that the remove sed regex does not require you to check the line before: if the line is ending with the single ...= "Admin"
simply nothing happens.
UPDATE-2: taking my cue from Ed Morton, you can also put these two functions in your ${HOME}/.bashrc
(or ${HOME}/.profile
or equivalent if you use another shell):
function AddUser18() { USER="${1}"; FILE="${2}"; sed -i~ '18s/=\s*{\?\s*\(".*"\)\s*}\?\s*/= {\1, "'"${USER}"'"}/' "${FILE}"; }
function DelUser18() { USER="${1}"; FILE="${2}"; sed -i~ '18s/=\s*{\s*\("[^"]*"\)\s*,\s*\("'"${USER}"'"\)\s*}/= \1/; 18s/=\s*{\s*\("[^"]*"\)\(.*\)\(\s*,\s*"'"${USER}"'"\)\(.*\)\s*}/= { \1\2\4 }/' "${FILE}"; }
I've added the -i~
switch to sed to modify the file in place and make a backup copy of the prevision version with a '~'
suffix.
You cam use these function simply in this way:
source ~/.bashrc # only if you are using the same shell where you modify .bashrc
AddUser18 user3 "path/to/the/file" # to add a new user
DelUser18 user3 "path/to/the/file" # to remove it (or another)
If the file you want to modify it's always the same you can also hardcode the path in the function (so FILE will be FILE="path/to/the/file"
in both functions).
Upvotes: 1
Reputation: 21213
I prefer to use perl, but this should work with sed(1)
in a similar way.
To append an entry $entry
(remember to escape the $
with a backslash):
perl -pe 's/= {?("[^"]*"(, "[^"]*")*)}?/= {$1, "\$entry"}/' input_file
To delete a given entry $entry
:
perl -pe 's/({)?"\$entry"(?:, |(})?)/$1$2/g; s/, }/}/g; s/{("[^"]+")}/$1/g' input_file
Tested with some sample data and it seems to be working. The command to delete is smart enough to delete entries from the middle of a list, from the beginning, and from the end; it is also smart enough to transform entry lists that are 1 element after removal from {"entry"}
back to "entry"
, which is what seems to be desirable (since the input file with 1 entry does not group it inside brackets). If that's not the case, it's easy to remove it (let me know if you have trouble).
UPDATE
You mentioned in the comments that you can't use perl because it is not available in the machine this needs to run. As I expected, it will work in sed(1)
in a very similar way; you just have to use \1
and \2
for the capturing group variables instead of $1
and $2
, and use sed's extended regex mode by invoking it with the -E
flag.
To append an entry:
sed -E 's/= {?("[^"]*"(, "[^"]*")*)}?/= {\1, "\$entry"}/' input_file
To delete an arbitrary entry (again, it is smart enough to know how to delete from the beginning, end or middle of an entry list):
sed -E 's/({)?"\$entry"(, |(})?)/\1\3/g; s/, }/}/g; s/{("[^"]+")}/\1/g' input_file
Upvotes: 1
Reputation: 11216
The problem with your command is that you are replacing the first }
in the line for $entry
.
You could do the whole thing in one expression to avoid this
To add:
sed 's/= \([^=]*\)/= {\1, "'"$entry"'" }/' file
To remove:
sed 's/= {\("[^"]*"\), "'"$entry"'" }/= \1/' file
.
These assume that there is only one occurence on the line and that attempt
can be different strings.
Upvotes: 2
Reputation: 14949
You can try this sed
,
Add:
sed 's/\(= *\)"\([^"]\+\)"/\1{"\2", "$entry"}/' yourfile
Remove:
sed 's/\(= *\){"\([^"]\+\)", "$entry"}/\1"\2"/g' yourfile
Test:
$ cat f1
entry({"servicectl"}, alias("servicectl", "status")).sysauth = "Admin"
$ sed 's/\(= *\)"Admin"/\1{"Admin", "$entry"}/' f1 > f2
$ cat f2
entry({"servicectl"}, alias("servicectl", "status")).sysauth = {"Admin", "$entry"}
$ sed 's/{"Admin", "$entry"}/"Admin"/g' f2
entry({"servicectl"}, alias("servicectl", "status")).sysauth = "Admin"
Upvotes: 2