1

I would like to test a file for a string and an array of strings. My problem is the order of things.

If there is a line "Alice was shot by Bob" my code calls both players dead even if Bob is still alive. So I only want to test for "${player} ${deaths}" and ignore any "${player}" after ${deaths}.

An example line from the log file:

18:45:23 [Server/Thread][INFO] Alice was shot by Bob using weapon

The code should recognise "Alice" and "was shot by" but not "Bob" because "Bob" is after the death message. If there is only a death message or a player name it should do nothing which it currently does. It should also ignore the "using weapon" and the "server stuff" before Alice.

This is what I got so far:

#!/bin/bash
# testing for death messages and performing separate actions for each player

screenlog="screen.log" # this is a growing logfile
tmpscreenlog="tmpscreen.log" # this is always the last line of screenlog

player01="Alice" # the first player
player02="Bob" # the second player

deaths=( # an array of possible death messages
  "was shot by"
  "burned to death"
  "starved to death"
  "drowned"
)

while true; do
tail -n1 ${screenlog} >> ${tmpscreenlog} # this line creates a one line buffer from the growing screenlog file
  if [[ ! -z $(grep "${player01}\|${deaths[*]}" "${tmpscreenlog}") ]]; then # if Alice and any death occurs in tmpscreen.log
    echo "Alice is dead!" # output Alices death and perform some commands
    screen -Rd sessionname -X stuff "ban ${player01} You died! Thank you for participating.$(printf '\r')"
  # commands for Alice
  fi
  if [[ ! -z $(grep "${player02}\|${deaths[*]}" "${tmpscreenlog}") ]]; then # if Bob and any death occurs in tmpscreen.log
    echo "Bob is dead!" # output Bobs death and perform some commands
    screen -Rd sessionname -X stuff "ban ${player02} You died! Thank you for participating.$(printf '\r')"
  # commands for Bob
  fi
rm ${tmpscreenlog} # this line removes the one line screenlog buffer
sleep 1s
done

Thank you for any suggestions and help <3

Simylein
  • 13
  • 3
  • 1
    This looks like a frightfully naïve approach to natural-language understanding. The normal active English sentence would be "Bob shot Alice" where obviously the object comes _after_ the verb phrase. Only passive sentences have the agent at the end. – tripleee Nov 06 '20 at 12:29
  • Check the regexs you craft (replace the `grep`s with `echo` or ideally run your script with `bash -x` or `set -x` and take the time to understand its output), they are far from correct, you've got `|` where you should have spaces and vice-versa – Aaron Nov 06 '20 at 12:33
  • Capture groups `grep "(?.*) ${deaths[*]} (?.*)\."`. (I'm assuming the ${deaths[*]} part works, I don't know that part.) The idea is to match the death phrase and use the capture group before to pull victim name. Bonus is the killer name is just as easy. – Jeter-work Nov 06 '20 at 14:03
  • @Xalorous "(I'm assuming the ${deaths[*]} part works" > it doesn't, it would expand to the death causes separated by space, while you'd need them separated by `|` and enclosed into `(...)` – Aaron Nov 06 '20 at 14:04

2 Answers2

2

tmpscreenlog="tmpscreen.log" # this is always the last line of screenlog

Hey, this is not always true... What if two (or more) messages appears in the last second?

It is better to use shell pipes to handle such things. You could use something like

tail -f screen.log | awk '/^[^ ]+ was (shot|slain|killed|blown up) by/ { print $1 " is dead" }'

Thanks tripleee for simplifying

Dmitry Lihachev
  • 504
  • 6
  • 20
1

I would use the following pipeline to replace your whole script :

tail -n0 -f screen.log | sed -nE 's/.* ([A-Za-z]+) was (shot|slain|killed|blown up) by.*/\1 is dead/p' 

The sed command will match lines that conform to your format, capturing the name of the dead player in the first capturing group, and replace those lines by your desired death message.

tail's -f option "follows" the file, outputting content as it is added to the log file and removing the need for a while loop.
I'm using -n0 to avoid matching lines that were present before your executed the command. If that's not a desired feature just remove it, it'll match by default from the 10 last lines of the file.

You can try it here.

If you're using GNU grep you could also rely on a lookahead to extract the killed player name alone :

grep -Po '\w+(?= was (shot|slain|killed|blown up) by)'
Aaron
  • 24,009
  • 2
  • 33
  • 57
  • sed will keep garbage messages, so it is better to use `grep` – Dmitry Lihachev Nov 06 '20 at 12:31
  • No, `sed -n` specifically turns off printing of messages which did not match. – tripleee Nov 06 '20 at 12:32
  • `sed` does not portably support `\w` – tripleee Nov 06 '20 at 12:32
  • @DmitryLihachev check my "try it here" link if you need to be convinced. That said it's probably better for anyone new to `bash` to learn to use `awk` than `sed`, so I'd suggest OP goes with your answer – Aaron Nov 06 '20 at 12:35
  • Thank you for your code. Maybe my Script was really stupid but if there are like 16+ players and 40+ different death messages how do I put that? Can I just expand your code with all the player names and death messages? And the thing is if Alice dies I want to send some commands to a screen session like this: `screen -Rd sessionname -X stuff "ban Alice You died! Thank you for participating.$(printf '\r')"` – Simylein Nov 06 '20 at 13:43
  • @Simylein Your script wasn't stupid and was a good attempt, don't worry, the regexs you crafted were just incorrect and the whole thing could be simplified. Re the player names : it might not be a problem seeing how I capture them with `[A-Za-z]+` which matches any word ; you might need to expand that character class if the names can contain numbers or non-alphanumeric characters (e.g. the current version wouldn't match "O'Maley"). Re the death causes : you can freely add causes to the to alternation, or maybe you could come up with a more generic solution (cont.) – Aaron Nov 06 '20 at 13:48
  • For instance if the only lines that have the ` was by` pattern are the deaths line you could use `([A-Za-z]+) was .* by` to match them, and at this point it should work even if there are new deaths causes in the game. Re the `screen` : you can use `xargs` to execute the command for each line of output the `sed` command produces. A comment wouldn't be practical to describe the whole solution, but `xargs` is well documented and you could always ask another question here if you have a hard time using it – Aaron Nov 06 '20 at 13:50
  • Just saw your most recent edit, looks like deaths cause do not always contain "by". Listing all the deaths causes inside an alternation would work fine (`([A-Za-z]+ (was shot|burned to death|...)`) and if you'd prefer to specify them inside an array you could use one of the solutions of [this question](https://stackoverflow.com/questions/1527049/how-can-i-join-elements-of-an-array-in-bash) to join them by `|` in order to generate the regex. `${deaths[*]}` doesn't work because it joins them with spaces, which isn't what you want in your regex – Aaron Nov 06 '20 at 13:58
  • @Aaron Thank you for your help, I will read some xargs manuals. – Simylein Nov 06 '20 at 14:00