将并发 Bash 代码移植到 Powershell

Porting concurrent Bash code to Powershell

我有以下用于写入存储的函数(sync 在 Unix 上):

# Sync Progress Function
function syncStorage {

  printq "Writing storage, may take more than 5 minutes."
  printq "Although it seems slow, consider this process like flashing an ISO to a USB Drive."
  printq "Below is an innacurate indicator of mB left to write. It may decrease hundreds of megabytes in seconds."

  # shellcheck disable=SC2016
  sync & {
    # If the unsynced data (in kB) is greater than 50MB, then show the sync progress
    while [[ $(grep -e Dirty: /proc/meminfo | grep --color=never -o '[0-9]\+') -gt 5000 ]]; do
      SYNC_MB=$(grep -e Dirty: /proc/meminfo | grep --color=never -o '[0-9]\+' | awk '{/=1024;printf "%.2fMB\n",}')
      echo -en "\r${SYNC_MB}"
      sleep 1
    done
  }

  echo

  #watch -n 1 'grep -e Dirty: /proc/meminfo | grep --color=never -o '\''[0-9]\+'\'' | awk '\''{/=1024;printf "%.2fMB\n",}'\'''
  #grep -e Dirty: /proc/meminfo | grep --color=never -o '[0-9]\+' | awk '{/=1024;printf "%.2fMB\n",}'
}

我想将它移植到 Powershell,到目前为止已经这样做了:

sync & {
      # If the unsynced data (in kB) is greater than 50MB, then show the sync progress
      # You can replace contents in $(...) with 5001 for testing purposes
      while ( $(grep -e Dirty: /proc/meminfo | grep --color=never -o '[0-9]\+') -gt 5000 ) {
        SYNC_MB=$(grep -e Dirty: /proc/meminfo | grep --color=never -o '[0-9]\+' | awk '{/=1024;printf "%.2fMB\n",}')
        echo -en "\r${SYNC_MB}"
        sleep 1
      }
    }

Powershell 接受此语法,但 returns:

Id     Name            PSJobTypeName   State         HasMoreData     Location
--     ----            -------------   -----         -----------     --------  
45     Job45           BackgroundJob   Running       True            localhost 

      # If the unsynced data (in kB) is greater than 50MB, then show the sync progress
      while ( 43289423 -gt 5000 ) {
        SYNC_MB=$(grep -e Dirty: /proc/meminfo | grep --color=never -o '[0-9]\+' | awk '{/=1024;printf "%.2fMB\n",}')
        echo -en "\r${SYNC_MB}"
        sleep 1
      }
    

它没有 运行 代码,它只是打印出来。当您将 $(...) 替换为 while 循环条件中的任何值(例如 5001 以满足 while 循环)时,相同的行为仍然存在。有什么想法吗?

虽然表面上相似,Bash 与 PowerShell 中的 { ... } 根本不同:

  • 在Bash中,它是一种将要执行的语句分组的方法立即,直接在调用者的范围内。

  • 在 PowerShell 中,它是一种将语句分组为 script block, which is a reusable unit of code to be executed on demand, either in a child scope, via &, the call operator, or directly in the caller's scope, via ., the dot-sourcing operator.

    的方法
    • 如果您像您一样单独输出脚本块,不会执行并且其逐字内容(无 {})打印到显示器。

相比之下,在两个 shell 中,post-positional & 用于异步启动 background job,在 PowerShell(仅限 Core)中相当于使用 Start-Job cmdlet。

因此,以下 Bash 调用模式:

# Bash: These are really two separate statements:
#       `sync &` to launch `sync` in the background, 
#       and `{ ...; }` to execute commands synchronously in the foreground.
# For clarity, you could place the statements on separate lines.
sync & { ...; }

对应于 PowerShell 中的以下内容:

# PowerShell:
# Note: You may prefer `&` to `.` in order to create a child scope
#       whose variables are limited to that scope.
# Here too you could place the statements on separate lines.
sync & . { ... }

另外,您将 Bash 代码转换为 PowerShell 存在问题:

  • 与 Bash 不同,PowerShell 中的变量赋值:

    • 那里也需要 $ 印记;例如,$SYNC_MB = ... 而不是 SYNC_MB=...
    • 如上图所示,PowerShell 还允许空格包围 =assignment operator.
  • 与 Bash 不同,PowerShell 的 -gt 不完全是 数字 :它用作单个 greater-than运算符也适用于 string LHS 值,在这种情况下它执行 lexical 比较。

    • 因此,为了与作为外部程序输出的 LHS 进行数值比较,例如 grep,它总是 string,您需要将其转换为适当的数字类型,通常是[int];提供简单的例子:

      (/bin/echo 10) -gt 2 # !! $false - LEXICAL comparison
      
      [int] (/bin/echo 10) -gt 2 # OK: $true - NUMERICAL comparison
      
  • 与 Bash、$(...) 不同,subexpression operator is rarely needed outside expandable (double-quoted) string ("..."):

    • 在变量赋值中,根本不需要运算符。
    • 要使一个命令的输出参与到一个更大的表达式中,通常使用(...)比较好,grouping operator
  • echo 不是指外部 /bin/echo 实用程序,而是 Write-Output cmdlet 的 built-in 别名,它既不理解-en 选项和 \ 转义序列,因为 PowerShell 使用 `,so-called 反引号作为其转义字符。

    • 虽然您可以通过其完整路径调用 /bin/echo,但 PowerShell 方法是使用 Write-Host cmdlet,如下所示。

    • 或者,正如您所发现的,PowerShell 有一个专门用于进度显示的 cmdlet,Write-Progress,但请注意,它的缺点是它会显着降低整体操作速度。

  • 由于 long-standing 和非常不幸的错误,PowerShell - 至少 7.2.2 - 无法正确传递 " 个字符。嵌入在 external-program 参数中,需要它们的 手动 \-转义 ,甚至在 verbatim (single-quoted) string ('...') 中,例如在您的 awk 命令中。

    • 有关详细信息,请参阅

将它们放在一起,使用 & 执行脚本块 child-scoped,并假设 sync 是 PowerShell script (sync.ps1) 或 外部实用程序 ,将语句放在单独的行中:

sync &
& {
  while ([int] (grep -e Dirty: /proc/meminfo | grep --color=never -o '[0-9]\+') -gt 5000 ) {
    $SYNC_MB = grep -e Dirty: /proc/meminfo | grep --color=never -o '[0-9]\+' | awk '{/=1024;printf \"%.2fMB\n\",}'
    Write-Host -NoNewLine "`r${SYNC_MB}"
    sleep 1
  }
}

注意:如果 sync 是一个 PowerShell 函数 ,上面的方法将不起作用,因为后台作业不与调用者共享状态并且对其一无所知函数(通过 auto-loading modules 提供的函数除外)。