-1

I want to turn the following input:

May 13 00:29:49 BBAOMACBOOKAIR2 com.apple.xpc.launchd[1] (com.apple.mdworker.bundles[12610]): Service exited with abnormal code: 78
May 13 00:30:00 BBAOMACBOOKAIR2 syslogd[113]: Configuration Notice:
    ASL Module "com.apple.cdscheduler" claims selected messages.
    Those messages may not appear in standard system log files or in the ASL database.
May 13 00:30:00 BBAOMACBOOKAIR2 syslogd[113]: Configuration Notice:
    ASL Module "com.apple.install" claims selected messages.
    Those messages may not appear in standard system log files or in the ASL database.

into the following output:

May 13 00:29:49 BBAOMACBOOKAIR2 com.apple.xpc.launchd[1] (com.apple.mdworker.bundles[12610]): Service exited with abnormal code: 78
May 13 00:30:00 BBAOMACBOOKAIR2 syslogd[113]: Configuration Notice:ASL Module "com.apple.cdscheduler" claims selected messages.Those messages may not appear in standard system log files or in the ASL database.
May 13 00:30:00 BBAOMACBOOKAIR2 syslogd[113]: Configuration Notice:ASL Module "com.apple.install" claims selected messages.Those messages may not appear in standard system log files or in the ASL database.

That is, the indented lines should be joined to the preceding non-indented line.

I already have a PowerShell solution, but now I need a solution using native macOS utilities, such as a bash solution.

Here is the PowerShell solution, from this answer to my previous question:

  $mergedLine = ''
  switch -Regex -File file.log {
    '^\S' {  # 'May ...' line, no leading whitespace.
      if ($mergedLine) { $mergedLine } # output previous 
      $mergedLine = $_
    }
    default { # Subsequent, indented line (leading whitespace)
      $mergedLine += ' ' + $_.TrimStart()
    }
  }
  $mergedLine # output final merged line

Here is my attempt at converting this to a bash script:

file=/xx
OIFS=$IFS
IFS=
while read -r line
do
case $line in
        [a-zA-Z]*)
        if [ $line ];then
        line=$line
        fi
        y=$line
        ;;
        *)
        line=$y$line
        ;;
esac
echo $line
done <$file
IFS=$OIFS

Unfortunately, it isn't working as intended, because I receive the following output:

May 13 00:29:49 BBAOMACBOOKAIR2 com.apple.xpc.launchd[1] (com.apple.mdworker.bundles[12610]): Service exited with abnormal code: 78
May 13 00:30:00 BBAOMACBOOKAIR2 syslogd[113]: Configuration Notice:
May 13 00:30:00 BBAOMACBOOKAIR2 syslogd[113]: Configuration Notice: ASL Module "com.apple.cdscheduler" claims selected messages.
May 13 00:30:00 BBAOMACBOOKAIR2 syslogd[113]: Configuration Notice: Those messages may not appear in standard system log files or in the ASL database.
May 13 00:30:00 BBAOMACBOOKAIR2 syslogd[113]: Configuration Notice:
May 13 00:30:00 BBAOMACBOOKAIR2 syslogd[113]: Configuration Notice: ASL Module "com.apple.install" claims selected messages.
May 13 00:30:00 BBAOMACBOOKAIR2 syslogd[113]: Configuration Notice: Those messages may not appear in standard system log files or in the ASL database.
tripleee
  • 175,061
  • 34
  • 275
  • 318
Kevin
  • 35
  • 5
  • Good hear. Fyi: PowerShell (Core) also [runs on a mac](https://learn.microsoft.com/en-us/powershell/scripting/install/installing-powershell-core-on-macos) – iRon Sep 08 '21 at 18:27
  • 1
    @iRon I know it can. but I still need the bash/shell. And if u may, please undo your vote to this question. thank you. – Kevin Sep 08 '21 at 18:30
  • A comment on the PowerShell version: you use the .Net class `System.IO.StreamReader` (rather then the native `Get-Content` cmdlet) probably for [performance reasons](https://learn.microsoft.com/powershell/scripting/dev-cross-plat/performance/script-authoring-considerations) but simular to [building object collections](https://stackoverflow.com/a/60708579/1701026), strings are immutable and therefore the increase assignment operator (`+=`) might become pretty expensive. Instead, you better drop each line on the pipeline and `-Join` them in once. – iRon Sep 08 '21 at 18:50
  • 1
    @iRon Yes, because of performance. the file is too huge. thank you for your both advice. I will learn how to ask a great question as well. – Kevin Sep 08 '21 at 18:54
  • Possible duplicate of https://stackoverflow.com/questions/21367445/concatenating-previous-line-with-current-line-based-on-condition – tripleee Sep 09 '21 at 04:42

2 Answers2

1
  • In the realm of Unix utilities, awk has fundamental conceptual similarities to PowerShell's switch statement[1], which the PowerShell solution is based on.

  • As an external, compiled utility, awk far outperforms any solution written in pure (bash) shell code, such as the while / case loop-based solution you tried in your question.

The equivalent (portable) awk solution is:

awk '
  /^[^[:blank:]]/ {                   # line starts with non-whitespace char.
    if (length(mergedLines)>0) { print mergedLines } # print previous merged line
    mergedLines = $0                  # start new merged line
    next 
  }
  {                                   # indented line
    sub(/^[[:blank:]]+/, "")          # trim leading whitespace
    mergedLines = mergedLines " " $0  # join to previous lines
  }
  END { 
    print mergedLines                 # print last merged line
  }
' file.log

Note that the awk version that comes with macOS is mostly limited to POSIX-mandated features, whereas GNU Awk (gawk) - installable on demand on Macs - offers many additional features, such as convenient character-class shortcuts \S and \s in lieu of ^[:blank:] and [:blank:] (though, technically, the strict equivalents are ^[:space:] and [:space:], but intra-line that doesn't make a difference), as also available in the .NET regex implementation that PowerShell utilizes.

tripleee suggests the following streamlined, more awk-idiomatic variant:

awk '
  /^[[:blank:]]/ {           # indented line
    sub(/^[[:blank:]]/, "")  # trim leading whitespace
    merged = merged " " $0   # join to previous lines
    next
  }
  merged {                   # previous merged line exists?
    print merged             # print previous
  }
  {                          # line starts with non-whitespace char.
    merged = $0              # start new merged line  
  }
  END {
    print merged             # print last merged line
  }
' file.log

[1] In fact, the much older awk inspired PowerShell's switch statement.

mklement0
  • 382,024
  • 64
  • 607
  • 775
0

Here is a solution that applies minimal modifications to your script to get it working correctly. However, I would recommend using @mklement0's awk based solution ahead of this one.

file=/xx
OIFS=$IFS
IFS=
while read -r line
do
    case $line in
        [a-zA-Z]*)
        if [ "$formatted_line" ];then
            echo "$formatted_line"
        fi
        formatted_line="$line"
        ;;
        *)
        formatted_line="$formatted_line $line"
        ;;
    esac
done <$file
if [ "$formatted_line" ];then
    echo "$formatted_line"
fi
IFS=$OIFS
joanis
  • 10,635
  • 14
  • 30
  • 40