Page 1 of 1

Problem with correctly quoting in a FOR /F loop

Posted: 21 Mar 2019 01:30
by jwoegerbauer
With regards to maximize the life span of an SSD - as modern PCs have it - I want to reduce writing to it. So my intention is to avoid creating / writing to / erasing temporay files: I want to handle it all in RAM.

My current problem is that

Code: Select all

"!adb!" shell "su -c 'cat /proc/!local_app_pid!/status'" > !tmp_file!
for /F "tokens=1,2 delims= " %%a in (!tmp_file!) do ...
works in a batch file, but this

Code: Select all

set "android_cmd=shell ^"su -c 'cat /proc/!local_app_pid!/status'^""
for /F "tokens=1,2 delims= " %%a in ('"!adb!" !android_cmd!') do ...
does not: batch file crashes.

Can anybody enlighten me how the set "android_cmd= " must be correctly coded?

FYI: Here "adb" is a Windows executable that via USB on an Android device invokes Android's command processor "shell" provisioning the command "su" with related parameters

Re: Problem with correctly quoting in a FOR /F loop

Posted: 21 Mar 2019 03:55
by aGerman
Sometimes quoted paths require CALL. Try ...

Code: Select all

... in ('call "!adb!" !android_cmd!') do ...
Also maybe you have to escape the single quotes that are in the value of !android_cmd!.

Steffen

Re: Problem with correctly quoting in a FOR /F loop

Posted: 21 Mar 2019 07:52
by dbenham
No, single quotes never need to be escaped. Something like FOR /F %%A IN ('someCommand 'someParameter' additionlParameter') DO ... works just fine.

Also, there should not be any need to CALL a command within the IN() clause, even if it happens to be a batch script.

The primary problem stems from the fact that FOR /F executes the command via CMD /C, and that plays games with double quotes. Below is the relevant documentation from HELP CMD:
partial HELP CMD output wrote: If /C or /K is specified, then the remainder of the command line after
the switch is processed as a command line, where the following logic is
used to process quote (") characters:
  1. If all of the following conditions are met, then quote characters
    on the command line are preserved:
    • no /S switch
    • exactly two quote characters
    • no special characters between the two quote characters,
      where special is one of: &<>()@^|
    • there are one or more whitespace characters between the
      two quote characters
    • the string between the two quote characters is the name
      of an executable file.
  2. Otherwise, old behavior is to see if the first character is
    a quote character and if so, strip the leading character and
    remove the last quote character on the command line, preserving
    any text after the last quote character.
The conditions for option 1 fail because the variables will have been expanded by the time CMD /C sees the text, and there are more than two quote characters. So option 2 is followed, and the quotes are corrupted.

If you don't really need quotes around, !adb!, then the simplest solution is to drop them. But I will assume they are there for a reason.

The solution is to place an extra set of quotes around the entire command that will be stripped by option 2 of CMD /C.

I think in your case you can simply do the following:

Code: Select all

for /F "tokens=1,2 delims= " %%a in ('""!adb!" !android_cmd!"') do ...
For the first round of parsing, the wrong text is quoted: !adb! is unquoted, and " !android_cmd!" is quoted. But there are no special characters, so there is no harm.

But a more generic option is to escape the first and last quote. This way you introduce the quotes needed by CMD /C without disturbing your quoting semantics.

Code: Select all

for /F "tokens=1,2 delims= " %%a in ('^""!adb!" !android_cmd!^"') do ...



There is one additional problem - You cannot escape a double quote once quoting has begun, so the following assignment actually preserves the first caret as a literal.

Code: Select all

set "android_cmd=shell ^"su -c 'cat /proc/!local_app_pid!/status'^""
The assigned value becomes

Code: Select all

shell ^"su -c 'cat /proc/!local_app_pid!/status'"
Because there are no poison characters in the assignment, you can simply drop the escape.

Code: Select all

set "android_cmd=shell "su -c 'cat /proc/!local_app_pid!/status'""
Or you can escape the outer set of quotes (I think this is the most robust form that is generally applicable)

Code: Select all

set ^"android_cmd=shell "su -c 'cat /proc/!local_app_pid!/status'"^"
Or you can drop the outer set of quotes, but then you run the risk of inadvertent trailing spaces being included in the assignment

Code: Select all

set android_cmd=shell "su -c 'cat /proc/!local_app_pid!/status'"

Dave Benham

Re: Problem with correctly quoting in a FOR /F loop

Posted: 21 Mar 2019 10:07
by aGerman
Great explanation, Dave. Hope I will remember it next time.

Steffen

Re: Problem with correctly quoting in a FOR /F loop

Posted: 23 Mar 2019 00:29
by jwoegerbauer
Thanks for your inputs, guys. To keep you updated: None of the proposed solutions were effective. Have a nice day.

Re: Problem with correctly quoting in a FOR /F loop

Posted: 23 Mar 2019 08:06
by Squashman
jwoegerbauer wrote:
23 Mar 2019 00:29
Thanks for your inputs, guys. To keep you updated: None of the proposed solutions were effective. Have a nice day.
Care to give us any valuable feedback?

Dave, I always thought if the code had single quotes that the USEBACKQ option was needed with the FOR /F???

Re: Problem with correctly quoting in a FOR /F loop

Posted: 23 Mar 2019 12:08
by dbenham
jwoegerbauer wrote:
23 Mar 2019 00:29
Thanks for your inputs, guys. To keep you updated: None of the proposed solutions were effective. Have a nice day.
I'm pretty sure the info I provided should work, unless there is something important that you have not shown us. It might help to know the exact value of variable adb.

Squashman wrote:
23 Mar 2019 08:06
Dave, I always thought if the code had single quotes that the USEBACKQ option was needed with the FOR /F???
Nope. FOR /F detects and removes the outer single quotes and executes the command, preserving any internal single quotes that might be there.

Code: Select all

C:\test>for /f %a in ('cmd /c echo 'hello'') do @echo %a
'hello'
The only time USEBACKQ is required is when you want to read a file and the file name contains white space that must be quoted. There is no other way to directly read such a file with FOR /F.

All strings and commands may be processed with or without USEBACKQ with pretty much equal ease - the USEBACKQ is redundant for strings and commands.

So I find it easier to only use USEBACKQ when reading a file.


Dave Benham

Re: Problem with correctly quoting in a FOR /F loop

Posted: 23 Mar 2019 23:56
by jwoegerbauer
@dbenham

To answer your question:

Behind term "!adb!" hides a qualified full-file-path-name that may contain spaces, or not. Hence it gets enclosed in quotes.
In my case it gets composed as follows:

Code: Select all

@echo off & setlocal ENABLEDELAYEDEXPANSION
pushd %CD%
...
set "adb_executable=adb-server.exe"
if !is32bit! equ 1 (
	set "adb=.\adb-1.0.36\32\!adb_executable!"
) else (
	set "adb=.\adb-1.0.36\64\!adb_executable!"
)
...
taskkill / IM "!adb_executable!" /T >nul
popd
endlocal & @echo on & exit
Probably OFF-TOPIC and not of interest at all:
• adb
An executable that resides on Windows host
• shell
The command processor on connected Android client: it executes the given shell command
• cmd
The shell command to be executed, may contain a subcommand, both may have an array of string arguments whereas an argument may have spaces in itself

That's default synthax - what generally is working, but not if used in FOR /F loop

Code: Select all

adb shell "cmd -c 'subcommand arguments'"