Announcement

Collapse
No announcement yet.

awk/sed/whatever bash question

Collapse
X
 
  • Filter
  • Time
  • Show
Clear All
new posts

    awk/sed/whatever bash question

    I wrote and maintain a set of Dolphin Service Menus to allow some btrfs functionality to be accessible from Dolphin.

    I am adding the ability to determine if a "btrfs send|btrfs receive" operation was successful. The success or failure is is indicated by two items retrievable from "btrfs su show". They are "Received UUID:" followed by a UUID and "FLAGS:" followed by "readonly". Both entries are only set if the send|receive operation completes successfully.

    One uses the "btrfs su show" action to see the full list of subvolume information, which looks like this:
    Code:
    sudo btrfs su show @KDEneon_backup
    Name:                   @KDEneon_backup
    UUID:                   00983575-f97d-d942-9537-f4c8c29bc77a
    Parent UUID:            0bef4f24-2620-7443-84d2-43c82a79e690
    Received UUID:          16716210-06d4-ea42-9048-7449068cb643
    Creation time:          2026-10-06 05:39:35 -0400
    Subvolume ID:           2246
    Generation:             14825
    Gen at creation:        14820
    Parent ID:              5
    Top level ID:           5
    Flags:                  readonly
    Send transid:           2145521
    Send time:              2026-10-06 05:39:35 -0400
    Receive transid:        14821
    Receive time:           2026-10-06 05:39:36 -0400
    Snapshot(s):
    Quota group:            n/a​
    If the send|receive operation fails, the fields after "Received UUID" and "Flags:" have a dash in them instead of the above.

    The question: Is there a preferred or "better" way to extract this information from the above output?

    Currently, I'm using:
    Code:
    RECEIVED_UUID=`sudo -S btrfs su sh $SOURCE | awk 'NR >4 && NR<=5 {print $3}'`
    READONLY_FLAG=`sudo -S btrfs su sh $SOURCE | awk 'NR >11 && NR<=12 {print $2}'`
    $SOURCE is the full path and filename of the selected subvolume. The above awk statements retrieve the Received UUID and "readonly" from Flags, and it works.

    I then use a conditional "if - or" to check for the dash instead of anything else:
    Code:
    if [[ $RECEIVED_UUID == '-' || $READONLY_FLAG == '-' ]]; then
    ​so if either entry is not set, the script reports that it failed or wasn't a "sent" snapshot.

    I feel like there must be a handful of other ways to scrape the info. Mostly, I'm just curious, but having "solid" code is always a good idea.

    Thoughts?

    Please Read Me

    #2
    More natural would be to search for the lines:
    Code:
    RECEIVED_UUID="$(sudo -S btrfs su sh "$SOURCE" | awk '/Received UUID:/{print $3}')"
    READONLY_FLAG="$(sudo -S btrfs su sh "$SOURCE" | awk '/Flags:/{print $2}')"
    The $() construct is preferred over backticks, and some folks always want quotes around bash variables and other $ substitutions.
    ​
    Regards, John Little

    Comment


      #3
      Google AI (not a guarantee of correctness, but this will likely; with your knowledge; point you in a productive direction)

      Yes. Your current approach works, but there are a couple of things I would change if the goal is **robustness rather than merely getting the current output to parse**.

      The biggest issue is that you're relying on **line numbers**:

      bash
      Code:
      NR >4 && NR<=5
      NR >11 && NR<=12
      That is fragile. If a future version of `btrfs-progs` adds, removes, or rearranges a field, your script could silently extract the wrong thing.

      I'd parse the **field names** instead.

      ### 1. A simple `awk` solution

      You can extract both values with **one invocation** of `btrfs` and one `awk`:

      bash
      Code:
      read -r RECEIVED_UUID READONLY_FLAG < <(
      sudo -S btrfs subvolume show "$SOURCE" |
      awk '
      /^Received UUID:/ { received=$3 }
      /^Flags:/ { flags=$2 }
      END { print received, flags }
      '
      )
      Then:

      bash
      Code:
      if [[ $RECEIVED_UUID == "-" || $READONLY_FLAG != "readonly" ]]; then
      echo "Send/receive failed or this is not a received snapshot."
      else
      echo "Send/receive successful."
      fi
      Notice that I would actually test `readonly` positively rather than test for `-`.

      That's a little more defensive:

      bash
      Code:
      [[ -n "$RECEIVED_UUID" && "$RECEIVED_UUID" != "-" &&
      "$READONLY_FLAG" == "readonly" ]]
      ### 2. An even cleaner approach: let `awk` make the determination

      Since you don't actually need the UUID or flag separately unless you use them elsewhere, you can have `awk` answer the question directly:

      bash
      Code:
      if sudo -S btrfs subvolume show "$SOURCE" |
      awk '
      /^Received UUID:/ { received = ($3 != "-") }
      /^Flags:/ { readonly = ($2 == "readonly") }
      END { exit !(received && readonly) }
      '
      then
      echo "btrfs send/receive succeeded."
      else
      echo "btrfs send/receive failed or this is not a received snapshot."
      fi
      I like this version for a service menu because the **exit status is the result**. There's no intermediate parsing state to manage.

      ---

      ### 3. You can also use `grep`/`awk` separately

      For example:

      bash
      Code:
      RECEIVED_UUID=$(sudo -S btrfs subvolume show "$SOURCE" |
      awk -F': *' '/^Received UUID:/ {print $2}')
      
      READONLY_FLAG=$(sudo -S btrfs subvolume show "$SOURCE" |
      awk -F': *' '/^Flags:/ {print $2}')
      This has an advantage over your current code because it doesn't care where those lines occur.

      However, **don't do this if performance matters** because you're running `btrfs subvolume show` twice. Your current code has the same issue.

      You can make it one call:

      bash
      Code:
      INFO=$(sudo -S btrfs subvolume show "$SOURCE")
      
      RECEIVED_UUID=$(awk -F': *' '/^Received UUID:/ {print $2}' <<< "$INFO")
      READONLY_FLAG=$(awk -F': *' '/^Flags:/ {print $2}' <<< "$INFO")
      ---

      ## One other thing I'd change

      You currently have:

      bash
      Code:
      RECEIVED_UUID=`sudo -S btrfs su sh $SOURCE | awk ...`
      I'd strongly recommend the modern command-substitution syntax:

      bash
      Code:
      RECEIVED_UUID=$(...)
      rather than backticks.

      And quote `$SOURCE`:

      bash
      Code:
      sudo -S btrfs subvolume show "$SOURCE"
      That protects you if the path ever contains spaces or shell metacharacters.

      Also, `su sh` is an abbreviation of `subvolume show`; I'd personally use the full form in a maintained script:

      bash
      Code:
      btrfs subvolume show
      It makes the script considerably easier for somebody else to understand.

      ---

      ## There's an interesting possibility here

      Because you're maintaining **Dolphin Service Menus**, I'd actually consider making the test slightly more semantic.

      You don't really care whether the fields contain `-`; what you care about is:

      > **Does this subvolume have the characteristics of a successfully received snapshot?**

      Based on the output you've shown, that could simply be:

      bash
      Code:
      if sudo -S btrfs subvolume show "$SOURCE" |
      awk '
      /^Received UUID:/ && $3 != "-" { received=1 }
      /^Flags:/ && $2 == "readonly" { readonly=1 }
      END { exit !(received && readonly) }
      '
      then
      # Successful received snapshot
      else
      # Not successfully received
      fi
      That has several nice properties:

      * Doesn't depend on line numbers.
      * Only invokes `btrfs` once.
      * Doesn't care about unrelated fields being added or removed.
      * Doesn't depend on the exact UUID format.
      * Explicitly expresses the two conditions you're interested in.
      * Uses the shell's normal success/failure mechanism.
      * If `btrfs subvolume show` itself fails, the pipeline will need a little additional handling if you want to distinguish **"btrfs command failed"** from **"snapshot isn't received."**

      ### One subtlety worth considering

      If you want this to be **really robust**, I'd also distinguish three states:

      1. `btrfs subvolume show` failed — invalid path, permissions, filesystem problem, etc.
      2. Command succeeded, but this isn't a successfully received snapshot.
      3. Command succeeded and both received-snapshot indicators are present.

      That prevents a failure to execute `btrfs` from being interpreted as merely "send/receive failed."

      For a production-quality Service Menu, I'd probably structure it that way.

      ​
      Slava Ukraini! 🇺🇦
      Windows no longer obstruct my view.
      Using Kubuntu Linux since March 23, 2007.
      "It is a capital mistake to theorize before one has data." - Sherlock Holmes​

      Comment


        #4
        A very thorough answer. Just one note:
        Code:
        RECEIVED_UUID=$(awk -F': *' '/^Received UUID:/ {print $2}' <<< "$INFO")
        Using -F: will put spaces on to the received UUID. A naive comparison with "-" would would fail.
        Regards, John Little

        Comment


          #5
          Google AI

          Exactly. Good catch. My -F': *' suggestion was incorrect for the comparison you have in mind.

          With:

          Code:
          awk -F': *' '/^Received UUID:/ {print $2}'
          the field separator matches the colon and the zero or more spaces immediately following it, but the behavior around the rest of the field means you should not rely on that to normalize the value for a "-" comparison.

          A cleaner solution is to use the whitespace-delimited fields, as you were originally doing:

          Code:
          RECEIVED_UUID=$(awk '/^Received UUID:/ {print $3}' <<< "$INFO")
          READONLY_FLAG=$(awk '/^Flags:/ {print $2}' <<< "$INFO")
          Given:

          Received UUID: 16716210-06d4-ea42-9048-7449068cb643
          Flags: readonly
          $3 is the UUID and $2 is readonly. If the value is:

          Received UUID: -
          then $3 is exactly -.

          Better still: don't extract the values at all

          For your particular test, I think this is the cleanest:

          Code:
          if awk '
          /^Received UUID:/ { received = ($3 != "-") }
          /^Flags:/ { readonly = ($2 == "readonly") }
          END { exit !(received && readonly) }
          ' <<< "$INFO"
          then
          echo "Successful btrfs receive"
          else
          echo "Not a successfully received snapshot"
          fi
          Or, if you want to retain the UUID because you use it later:

          Code:
          RECEIVED_UUID=$(awk '/^Received UUID:/ {print $3}' <<< "$INFO")
          READONLY_FLAG=$(awk '/^Flags:/ {print $2}' <<< "$INFO")
          
          if [[ $RECEIVED_UUID == "-" || $READONLY_FLAG != "readonly" ]]; then
          # failure
          fi
          I'd favor the latter if the UUID is useful elsewhere in your Service Menu.

          And there's an important improvement over your original code: you can obtain INFO once:

          Code:
          INFO=$(sudo -S btrfs subvolume show "$SOURCE")
          rather than executing sudo btrfs ... twice.

          So the complete version I'd use is:

          Code:
          INFO=$(sudo -S btrfs subvolume show "$SOURCE")
          
          RECEIVED_UUID=$(awk '/^Received UUID:/ {print $3}' <<< "$INFO")
          READONLY_FLAG=$(awk '/^Flags:/ {print $2}' <<< "$INFO")
          
          if [[ $RECEIVED_UUID == "-" || $READONLY_FLAG != "readonly" ]]; then
          echo "Send/receive failed or this is not a received snapshot."
          else
          echo "Send/receive succeeded."
          fi
          One other detail: I would use != "readonly" rather than == "-" for the Flags test. That makes the test mean what you actually intend: the subvolume must be readonly, regardless of how some future version of btrfs represents an unset flag​
          Slava Ukraini! 🇺🇦
          Windows no longer obstruct my view.
          Using Kubuntu Linux since March 23, 2007.
          "It is a capital mistake to theorize before one has data." - Sherlock Holmes​

          Comment

          Users Viewing This Topic

          Collapse

          There is 1 user viewing this topic.

          Working...
          X