배쉬++ 파싱

6287 단어 devopslinuxbash

소개



오늘 아침에 아들과 산책을 하다가 개선이 필요한 Bash 문제를 제안해 달라고 했습니다. 그는 정규식 구문 분석에 대해 언급했는데, 나는 그것이 잡일이라는 데 동의합니다. 그 후 나는 앉아서 이것을 위해 API와 같은 편리한 '읽기'를 제공하는 'regex_read' 함수를 작성했습니다. 아래에서 'regex_read' 기능을 더 소개하겠지만 먼저 내장된 'read' 명령을 사용하여 구문 분석을 검토해 보겠습니다.

'읽기'로 구문 분석



Bash에서 입력을 구문 분석하는 일반적인 방법은 내장 '읽기' 명령을 사용하는 것입니다. 이 명령은 한 번에 한 줄(또는 레코드)을 읽은 다음 토큰을 변수로 분할합니다. 다음과 같은 일반적인 예를 고려하십시오.

while read TOK1 TOK2 ALL_ELSE; do
# Do something with $TOK1 and $TOK2
done </some/input/file

여기에서 파일 '/some/input/file'이 열리고 이 루프의 범위 내에서 쉘의 표준 입력에 연결되어 '읽기'에 대한 입력을 제공합니다. 이 예에는 다음과 같은 묵시적 ​​기본값이 있습니다.
  • 'read'를 호출할 때마다 기본적으로 개행 문자인 구분자 문자를 포함하여 최대 바이트를 소비합니다. 이 값은 '-d' 스위치로 변경할 수 있습니다.
  • 'read'는 입력 필드 구분자 변수의 내용인 $IFS에 따라 각 레코드를 토큰으로 분할합니다.

  • 일반적으로 $IFS에는 인쇄할 수 없는 문자가 포함됩니다. 해당 문자가 무엇인지 알고 싶다면 'printf' 내장 명령이 도움이 됩니다.

    # Issue command
    printf '%q\n' "$IFS"
    $' \t\n'
    

    이제 $IFS에 공백, 탭 및 줄 바꿈이 포함되어 있음을 알 수 있습니다. CSV 파일을 구문 분석해야 하는 경우 다음과 같은 것이 필요합니다.

    IFS=$',\n'
    read COL1 COL2 COL3 COL4 ALL_ELSE
    

    이 구문은 상당히 간결하고 편리합니다. 당면한 문제에 대해 작동한다면 수정할 것이 없습니다. 그러나 때때로 구분 문자와 $IFS의 내용을 지정할 수 있는 것만으로는 충분하지 않습니다. 이 문제에 직면했다면 정규식 구문 분석이 최선의 선택입니다.

    정규식으로 구문 분석



    정규식을 사용한 구문 분석은 복잡한 입력 데이터에 대한 일반적인 접근 방식입니다. Bash에서 정규식으로 구문 분석하는 것은 약간 투박하며 다음과 같습니다.

    while IFS= read; do
       [[ $REPLY =~ systemd\[([^:]*)\]:\ (.*)$ ]] || continue
       full_match="${BASH_REMATCH[0]}"
       pid="${BASH_REMATCH[1]}" 
       msg="${BASH_REMATCH[2]}"
       echo "systemd message: pid= '$pid', msg= '$msg'"
    done </var/log/syslog
    

    보시다시피 $BASH_REMATCH[]에서 토큰을 낚시하는 것은 일상적인 일입니다. 이 예에서 '읽기'가 토큰을 분할하는 것을 방지하기 위해 $IFS가 없음으로 설정되어 있다는 점은 주목할 만합니다. $REPLY는 레코드를 검색하는 데 사용됩니다. 제공되지 않은 경우 문서화된 기본 반환 변수입니다.

    'regex_read'로 구문 분석



    이전 게시물에서 Bash++를 소개했습니다. 이제 그 기능을 사용하여 번거로운 작업을 제거하겠습니다.

    #!/bin/bash
    ############################################################
    # Example script to demonstrate bash++ regex_read function
    #
    # John Robertson <[email protected]>
    # Initial release: Mon Sep 14 10:29:20 EDT 2020
    #
    
    # Halt on error, no globbing, no unbound variables
    set -efu
    
    # import oop facilities and other goodies
    source ../bash++
    
    ###################################
    ### Execution starts here #########
    ###################################
    
    # Open file to supply input on file descriptor $FD.
    # Use recent bash syntax to assign next unused file descriptor to variable FD.
    # We do this so all standard streams remain available inside of loop for
    # interactive commands.
    exec {FD}</var/log/syslog
    
    # Loop until no more data available from $FD
    # Regex matches 'systemd' syslog entries, breaks out date stamp, pid, and message
    while regex_read '^([^ ]+ [^ ]+ [^ ]+) .*systemd\[([^:]*)\]: (.*)' -u $FD; do
    
       # First fetch the number of matches from the return stack.
       RTN_pop n_matches
    
       # Not interested in less than perfect match
       (( n_matches == 4 )) || continue
    
       # Pop match results into variables
       RTN_pop full_match dateStamp pid msg
       # Clear the terminal
       clear
    #  "Full match is: '$full_match'"
       echo "systemd message: pid= '$pid', dateStamp= '$dateStamp',  msg= '$msg'"
    
       # Use builtin bash menuing to branch on user's choice
       PS3='Action? '
       select action in 'ignore' 'review' 'quit'; do
    
          case $action in
    
             ignore) ;; # no worries
    
             review) read -p 'Chase up all relevant information, present to user. [Return to continue] ';;
    
             quit) exit 0;;
    
          esac
    
          # go get another line from syslog
          break
       done # End of 'select' menu loop
    
    done
    

    이 예에는 다음과 같은 몇 가지 관심 사항이 있습니다.
  • 입력 파일이 열리고 $FD에 있는 사용되지 않은 파일 설명자가 할당됩니다. 이렇게 하면 'regex_read' 루프 내에서 사용자 상호 작용에 사용할 수 있는 셸의 stdin이 남습니다.
  • 'regex_read' 구문은 regex 자체가 첫 번째 인수여야 한다는 점을 제외하고는 'read'와 같습니다. 첫 번째 인수 이후의 모든 인수는 '읽기'로 전달됩니다. 경기 결과는 모두 반환 스택에 배치됩니다.
  • 내장 'select' 명령은 루프 내에서 사용자에게 메뉴를 표시하고 사용자의 선택에 따라 분기하는 데 사용됩니다.
  • 반환 스택을 사용하면 함수에서 다양한 수의 복잡한 반환 값을 수용할 수 있습니다.

  • 'regex_read' 구현



    'regex_read'의 구현은 본질적으로 '읽기' 자체에서 파생되므로 간단합니다. 여기있어:

    function regex_read ()
    ############################################################
    # Similar to bash 'read' builtin, but parses subsequent
    # read buffer using the supplied regular expression.
    # Arguments:
    #   regex pattern
    # Returns:
    #   logical TRUE if read was successful
    #   or logical FALSE on end-of-file condition.
    # Return stack:
    #   Full string match (if any)
    #   token1_match (if any)
    #   ...
    #   Last argument is _always_ number of matches found. Pop it first.
    #   
    {
       # Stash the regular expression
       local ndx count regex="$1"
    
       # All other args are for 'read'
       shift
    
       # Call read with other supplied args. Fails on EOF
       IFS= read $@ || return 1
    
       # Apply regular expression parsing to read buffer
       if [[ $REPLY =~ $regex ]]; then
          # Place results on return stack
          count=${#BASH_REMATCH[@]}
          for (( ndx= 0; ndx < count; ++ndx )); do
             RTN_push "${BASH_REMATCH[$ndx]}"
          done
          # Last stack arg is number of match results
          RTN_push $count
    
       else
          # regex failed to match
          RTN_push 0
       fi
    }
    

    결론



    Bash는 정규식을 사용하여 데이터를 구문 분석할 수 있습니다. 이 작업은 source'ingbash++으로 가져오는 'regex_read' 함수를 사용하여 훨씬 간단해집니다. 게시물의 모든 파일은 동일한Github repository에서 사용할 수 있습니다.

    좋은 웹페이지 즐겨찾기