I have the function below for writing storage (sync
on 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 '{$1/=1024;printf "%.2fMB\n",$1}')
echo -en "\r${SYNC_MB}"
sleep 1
done
}
echo
#watch -n 1 'grep -e Dirty: /proc/meminfo | grep --color=never -o '\''[0-9]\ '\'' | awk '\''{$1/=1024;printf "%.2fMB\n",$1}'\'''
#grep -e Dirty: /proc/meminfo | grep --color=never -o '[0-9]\ ' | awk '{$1/=1024;printf "%.2fMB\n",$1}'
}
I want to port it to Powershell, and have done this so far:
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 '{$1/=1024;printf "%.2fMB\n",$1}')
echo -en "\r${SYNC_MB}"
sleep 1
}
}
Powershell accepts this syntax, but 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 '{$1/=1024;printf "%.2fMB\n",$1}')
echo -en "\r${SYNC_MB}"
sleep 1
}
It doesn't run the code, it just prints it out. The same behavior persists when you replace the $(...) with any value (e.g. 5001 to satisfy the while loop) in the while loop's condition. Any ideas?
CodePudding user response:
While superficially similar, { ... }
in Bash vs. PowerShell are fundamentally different:
In Bash, it is a way to group statements to be executed instantly, directly in the caller's scope.
In PowerShell, it is a way to group statements into a 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.- If you output a script block by itself, as you did, no execution happens and its verbatim content (sans
{
and}
) prints to the display .
- If you output a script block by itself, as you did, no execution happens and its verbatim content (sans
By contrast, in both shells, a post-positional &
serves to asynchronously launch a background job, which in PowerShell (Core only) is the implicit equivalent of using the Start-Job
cmdlet.
Therefore, the following Bash call pattern:
# 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 & { ...; }
corresponds to the following in 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 & . { ... }
Separately, there are problems with your translation of your Bash code to PowerShell:
Unlike in Bash, variable assignments in PowerShell:
- require the
$
sigil there too; e.g.,$SYNC_MB = ...
instead ofSYNC_MB=...
- As the above juxtaposition shows, PowerShell also permits whitespace to surround
=
, the assignment operator.
- require the
Unlike in Bash, PowerShell's
-gt
isn't exclusively numeric: it serves as the single greater-than operator that also works with string LHS values, in which case it performs lexical comparison.Therefore, in order to perform numerical comparison with an LHS that is the output from an external program such as
grep
, which is invariably a string, you need to cast it to an appropriate number type, typically[int]
; to provide simple examples:(/bin/echo 10) -gt 2 # !! $false - LEXICAL comparison [int] (/bin/echo 10) -gt 2 # OK: $true - NUMERICAL comparison
Unlike in Bash,
$(...)
, the subexpression operator is rarely needed outside expandable (double-quoted) string ("..."
):- In variable assignments, you need no operator at all.
- To make a command's output participate in a larger expression, it is usually better to use
(...)
, the grouping operator
echo
doesn't refer to the external/bin/echo
utility, but is a built-in alias of theWrite-Output
cmdlet, which understands neither the-en
options nor\
-escaped escape sequences, given that PowerShell uses`
, the so-called backtick, as its escape character.While you could call
/bin/echo
via its full path, the PowerShell way is to use theWrite-Host
cmdlet, as shown below.Alternatively, as you've discovered, PowerShell has a dedicated cmdlet for progress displays,
Write-Progress
, but note that its downside is that it can slow the overall operation down noticeably.
Due to a long-standing and highly unfortunate bug, PowerShell - up to at least 7.2.2 - doesn't properly pass
"
chars. embedded in external-program arguments, necessitating their manual\
-escaping, even inside verbatim (single-quoted) string ('...'
), such as in yourawk
command.- See this answer for more information.
To put it all together, using &
for child-scoped execution of the script block, and assuming that sync
is either a PowerShell script (sync.ps1
) or an external utility, placing the statements on separate lines:
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 '{$1/=1024;printf \"%.2fMB\n\",$1}'
Write-Host -NoNewLine "`r${SYNC_MB}"
sleep 1
}
}
Note: If sync
were a PowerShell function, the above wouldn't work, because background jobs do not share state with the caller and know nothing about its functions (except functions provided via auto-loading modules).