3

I need to create a script that replaces all letters of a word after a certain word with an asterisk (*).  For example:

sed 's/Word:[^ ]\+/Word:*/' Desktop/script_test

But this script replaces the entire word with only one asterisk, while I want to replace all letters. How can I do that? For example, with this input:

Word: cat

I want to get

Word: ***

I am running Linux.

P.S. The input must be read from a text file and also saved to the same file.

migrated from serverfault.com Feb 21 '17 at 8:05

This question came from our site for system and network administrators.

4

You could do it one at a time in a loop:

sed -e :1 -e 's/\(Word: *[^ ]*\)[^ *]/\1*/;t1'

To edit the file in place and assuming all the characters to replace are single-byte, you can make both sed's stdin and stdout the file as in:

sed ... < file 1<> file

Or with GNU sed, use the -i flag as in:

sed -i ... file

(Though that will replace the file with a new one (though with the same name). With BSD sed, use -i '' instead of -i).

  • 2
    and, as a bonus, this will work with multiple Words on the same line as opposed to most other solutions here... – don_crissti Feb 21 '17 at 18:34
  • @StéphaneChazelas Are you using 2 separated -e in the sed command on purpose? Why not simply joining both: -e ':1;s/\(Word: *[^ ]*\)[^ *]/\1*/;t1'? – oliv Feb 22 '17 at 7:01
  • 2
    @oliv, in most sed implementations, :1;s... would define a label called 1;s... instead of one called 1 followed by a s command (hence the need for two -es). That even used to be required by POSIX making GNU sed (which honours the ; as a command separator instead of being part of the label name even in POSIX mode) non-compliant then. POSIX still requires you to not use ; after the : command, but does not require anymore sed implementations to treat ; as being part of the label name anymore. – Stéphane Chazelas Feb 22 '17 at 8:08
3

How about:

$ cat file
Word: cat
Word: foobar
$ sed -E 's/Word: *//; s/./*/g; s/^/Word: /' file
Word: ***
Word: ******

To modify the file in place, just use -i:

$ sed -E -i 's/Word: *//; s/./*/g; s/^/Word: /' file

Note that this won't work if Word: is not the first word of the line. If that's a problem, you can use this instead:

perl -pe 's/Word:\s*\K(\S+)/sprintf "*" x length($1)/e' file

That, in turn, assumes that you want the first Word: on each line and also that you want everything following it to be replaced by *. If you only want the first word after Word: to be replaced, you really should ask a new question or, at least, clarify this one.

  • 1
    This sed command has the drawback of expecting the keyword Word: at the beginning of the line – oliv Feb 21 '17 at 13:05
  • 2
    @oliv well yes, of course it does. That's what the question is asking for isn't it? – terdon Feb 21 '17 at 13:10
  • That's not explicitly said... But don't worry, there is nothing wrong with your answer. I just came across this limitation and saw that the other sed command from @SatoKatsura was better suited for my own purpose. – oliv Feb 21 '17 at 13:17
  • @oliv all answers here except the python one where you can give Word: as an option, including the one by Sato, require the presence of Word:. And it's mentioned very explicitly in the question as well. The whole point of the question is to replace only after Word: . Otherwise it would be just a trivial sed 's/./*/g'. It doesn't need to be at the beginning of the line, it just marks the position after which the substitution should occur. – terdon Feb 21 '17 at 13:37
  • 2
    Looks like you didn't get my point... Obviously the keyword is required to perform the replacement, but it is not said that this one must be at the beginning of the line. Try it by putting any character before the keyword. – oliv Feb 21 '17 at 13:55
1

You could to this using sed as follows:

echo 'Word: cat' |
sed -e '
   s/Word:/&\n/
   :loop
      s/\n\([^a-zA-Z]*\)[a-zA-Z]/\1*\n/
   tloop
   s/\n\([^a-zA-Z]*\)$/\1/
' 
1

You could use this time employing the hold space for manipulations, as:

sed -e '
   s/Word:/&\n/   # everything to the right of marker is asterisk zone
   h              # will need later on for reconstructing, so save orig
   s/.*\n//       # retain only the asterisk zone
   s/[a-zA-Z]/*/g # perform the asterisk operation
   H;g            # abut onto original data
   s/\n.*\n//     # peel off unwanted data
'
  • The question says, “replace[s] all letters of a word after a certain word”.  Your answer will modify all the words after the designated word (although, on the plus side, it doesn’t change the spaces between those words); also, it mangles lines that don’t even contain the trigger word. – G-Man Feb 21 '17 at 21:18
  • Well this is what happens when the specs are very general. For example, what should we understand by a word ? Does it mean an English word or an alphanumeric a la \w+ ? Normally for questions like these a varied input with the expected output must be give, whereas as you can see it's just Word: cat. No mention that Word: can be anywhere in a line, can be multiple times, or whatever. So your comments are better aimed at the OP to begin with IMHO. – Rakesh Sharma Feb 21 '17 at 21:53
1

Perl

echo 'Word: cat' |
perl -lpe '
   1 while s/(?<=Word:)([^a-zA-Z]*)([a-zA-Z]+)/$1 . "*" x length($2)/e;
'

Bash

eval "`echo 'NL=qsq' | tr 'qs' '\047\012'`"; # newline
echo 'Word: cat' |
while IFS= read -r line
do
   var=${line/Word:/Word:"$NL"}
   var1=${var#*"$NL"}     var2=${var%"$NL"*}
   var3=${var1//[a-zA-Z]/*}
   var4=${var2}${var3}
   printf '%s\n' "$line" "$var4"
done
  • Simpler perl: echo 'Word: cat' | perl -lpe 's/Word: \K.*/"*" x length($&)/e' – Satō Katsura Feb 21 '17 at 12:18
  • Please don't post separate answers for each variant you come up with. just include them in your original answer. – terdon Feb 21 '17 at 13:10
1

Python solution with more flexible command-line arguments:

#!/usr/bin/env python3
import sys, fileinput

def replace_after(haystack, needle, replacement):
    pos = haystack.find(needle)
    if pos >= 0:
        pos += len(needle)
        return haystack[:pos] + (replacement * (len(haystack) - pos))
    else:
        return haystack

if __name__ == '__main__':
    needle, replacement = sys.argv[1:3]
    del sys.argv[1:3]

    with fileinput.input() as finput:
        for line in finput:
            print(replace_after(line.rstrip('\n'), needle, replacement))

Usage:

python3 unix.se.346510.py <NEEDLE> <REPLACEMENT> [FILES...]

Example:

$ printf '%s\n' 'Word: foo' 'more Word: bar-baz' | python3 unix.se.346510.py 'Word: ' '*'
Word: ***
more Word: *******
1

An awk solution and a shell solution:

echo '"Hello: World"' | awk '{ gsub("[^\"]", "*", $2); print }'

We have to escape the " in [^\"] since the regular expression itself is in double quotes. This generates

"Hello: *****"

With the shell (at least bash and ksh93):

echo '"Hello: World"' | {
  read -r prefix rest
  printf '%s %s\n' "$prefix" "${rest//[^\"]/*}"
}

Assuming the text "Hello: World" is on a line of its own, but embedded in a larger text in the file greetings.txt:

awk '/"Hello: [^"]*"/ { gsub("[^\"]", "*", $2) } { print }' greetings.txt >greet.tmp && mv greet.tmp greeting.txt

or (again, with bash or ksh93),

while read -r prefix rest; do
    if [[ "$prefix" =~ ^\"Hello: ]]; then
        rest=${rest//[^\"]/*}
    fi
    printf '%s %s\n' "$prefix" "$rest"
done <greetings.txt >greet.tmp
mv greet.tmp greetings.txt

For the input

Specific recipients:
"Hello: Mom!"
"Hello: Cat!"

General recipients:
"Hello: World!"

these two solutions generate

Specific recipients:
"Hello: ****"
"Hello: ****"

General recipients:
"Hello: ******"
  • The shell solution requires =~ as found e.g. in bash. – phk Feb 21 '17 at 18:38
  • @phk I failed to mention that, yes. I will add this. – Kusalananda Feb 21 '17 at 18:38
  • The question says, “replace[s] all letters of a word after a certain word”.  Your first answer will modify the second word of every line.  Your second answer will modify the second and all following words of every line. Also, your second and third answers will replace spaces after the second word, although the question indicates that spaces should not be replaced, and all your answers leave quote (") characters unchanged, with no obvious justification.    … (Cont’d) – G-Man Feb 21 '17 at 19:44
  • (Cont’d) … All your answers mangle spaces that aren’t replaced. Your third answer at least checks a preceding word, but (a) works only if the key word is the first word on the line (so it fails on I said, "Hello: World"), (b) requires that the Hello be preceded by a quote, with an equal absence of obvious justification, and (c) will match "Hello:;,./ World". – G-Man Feb 21 '17 at 19:45
  • @G-man My answer was to a version of the question that specified changing "Word: cat" into "Word: ***". That's where the double quotes comes from. I also only have the data provided by the OP, so I (we) don't actually know in what context this is found (by itself, or part of a line). – Kusalananda Feb 21 '17 at 19:53
1

Stuff like this goes better with flex:

%option 8bit main
%s star
%%
Word:           ECHO;  BEGIN star;
<star>[^ \t\n]+ memset(yytext,'*',yyleng); ECHO; BEGIN 0;

put that in say star.l, then make star.

(edit: states are vast overkill for this. better:

%option 8bit main
%%
Word:[^ \t\n]* memset(yytext+5,'*',yyleng-5); ECHO;

)

0

Using awk, you don't mention, specific use of sed :)

echo "Word: cat" | awk '{printf $1" "; for (i=1;i<=length($2);i++) printf "*"}'

Result:

Word: ***

  • The question says, “replace[s] all letters of a word after a certain word”. This code will modify the second word of every line; e.g., fat catfat ***. Also, this code will change Here is the word: cat to Here **. – G-Man Feb 21 '17 at 18:33

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.