Windows Batch File, build multiline string literal across multiple lines - batch-file

I have a batch file (called single-line.bat) with the following contents
python -c "import math; print(math.hypot(3, 4))"
I want the argument to python to be a multiline string that is constructed across multiple lines of the batch file. That is, I'd like the contents to resemble this
python -c "import math
print(
math.hypot(
3,
4)
)"
That is, I want to build a string literal that contains multiple lines. I also want to build that string literal across multiple lines.
This answer builds a command across multiple lines, not a string literal: https://superuser.com/a/1025263
This answer builds a string across multiple lines, but the string itself does not contain multiple lines: https://superuser.com/a/1596233
This answer comes close, but since it does not use quotes, it looks like I to escape batch characters such as %.: Passing around multi-line strings
To be sure, the following is a valid python script:
import math
print(
math.hypot(
3,
4)
)
and that is the script that I want to pass as an argument to the -c flag of the python command in my batch file.
[EDIT]
An answer suggests making a file proposal-1.bat
python -c "import math"^
"print("^
"math.hypot("^
"3,"^
"4)"^
")"
This doesn't work. Compared to the original single-line.bat:
> single-line.bat
> python -c "import math; print(math.hypot(3, 4))"
5.0
> proposal-1.bat
> python -c "import math" "print(" "math.hypot(" "3," "4)" ")"
I do not see any output python.

I cannot successfully post this as a comment with the intended formatting, so here it is as a potential answer instead.
Have you tried:
python -c "import math;"^
"print("^
"math.hypot("^
"3,"^
"4)"^
")"
Please note that the leading spaces on the continuation lines are intentional.

Related

CMake: how to avoid escaping spaces in command line? [duplicate]

I'm trying to create a custom command that runs with some environment variables, such as LDFLAGS, whose value needs to be quoted if it contains spaces:
LDFLAGS="-Lmydir -Lmyotherdir"
I cannot find a way to include this argument in a CMake custom command, due to CMake's escaping rules. Here's what I've tried so far:
COMMAND LDFLAGS="-Ldir -Ldir2" echo blah VERBATIM)
yields "LDFLAGS=\"-Ldir -Ldir2\"" echo blah
COMMAND LDFLAGS=\"-Ldir -Ldir2\" echo blah VERBATIM)
yields LDFLAGS=\"-Ldir -Ldir2\" echo blah
It seems I either get the whole string quoted, or the escaped quotes don't resolve when used as part of the command.
I would appreciate either a way to include the literal double-quote or as an alternative a better way to set environment variables for a command. Please note that I'm still on CMake 2.8, so I don't have the new "env" command available in 3.2.
Note that this is not a duplicate of When to quote variables? as none of those quoting methods work for this particular case.
The obvious choice - often recommended when hitting the boundaries of COMMAND especially with older versions of CMake - is to use an external script.
I just wanted to add some simple COMMAND only variations that do work and won't need a shell, but are - I have to admit - still partly platform dependent.
One example would be to put only the quoted part into a variable:
set(vars_as_string "-Ldir -Ldir2")
add_custom_target(
QuotedEnvVar
COMMAND env LD_FLAGS=${vars_as_string} | grep LD_FLAGS
)
Which actually does escape the space and not the quotes.
Another example would be to add it with escaped quotes as a "launcher" rule:
add_custom_target(
LauncherEnvVar
COMMAND env | grep LD_FLAGS
)
set_target_properties(
LauncherEnvVar
PROPERTIES RULE_LAUNCH_CUSTOM "env LD_FLAGS=\"-Ldir -Ldir2\""
)
Edit: Added examples for multiple quoted arguments without the need of escaping quotes
Another example would be to "hide some of the complexity" in a function and - if you want to add this to all your custom command calls - use the global/directory RULE_LAUNCH_CUSTOM property:
function(set_env)
get_property(_env GLOBAL PROPERTY RULE_LAUNCH_CUSTOM)
if (NOT _env)
set_property(GLOBAL PROPERTY RULE_LAUNCH_CUSTOM "env")
endif()
foreach(_arg IN LISTS ARGN)
set_property(GLOBAL APPEND_STRING PROPERTY RULE_LAUNCH_CUSTOM " ${_arg}")
endforeach()
endfunction(set_env)
set_env(LDFLAGS="-Ldir1 -Ldir2" CFLAGS="-Idira -Idirb")
add_custom_target(
MultipleEnvVar
COMMAND env | grep -E 'LDFLAGS|CFLAGS'
)
Alternative (for CMake >= 3.0)
I think what we actually are looking for here (besides the cmake -E env ...) is named Bracket Argument and does allow any character without the need of adding backslashes:
set_property(
GLOBAL PROPERTY
RULE_LAUNCH_CUSTOM [=[env LDFLAGS="-Ldir1 -Ldir2" CFLAGS="-Idira -Idirb"]=]
)
add_custom_target(
MultipleEnvVarNew
COMMAND env | grep -E 'LDFLAGS|CFLAGS'
)
References
0005145: Set environment variables for ADD_CUSTOM_COMMAND/ADD_CUSTOM_TARGET
How to modify environment variables passed to custom CMake target?
[CMake] How to set environment variable for custom command
cmake: when to quote variables?
You need three backslashes. I needed this recently to get a preprocessor define from PkgConfig and apply it to my C++ flags:
pkg_get_variable(SHADERDIR movit shaderdir)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DSHADERDIR=\\\"${SHADERDIR}\\\"")
Florian's answer is wrong on several counts:
Putting the quoted part in a variable makes no difference.
You should definitely use VERBATIM. It fixes platform-specific quoting bugs.
You definitely shouldn't use RULE_LAUNCH_CUSTOM for this. It isn't intended for this and only works with some generators.
You shouldn't use env as the command. It isn't available on Windows.
It turns out the real reason OPs code doesn't work is that CMake always fully quotes the first word after COMMAND because it's supposed to be the name of an executable. You simply shouldn't put environment variables first.
For example:
add_custom_command(
OUTPUT q1.txt
COMMAND ENV_VAR="a b" echo "hello" > q1.txt
VERBATIM
)
add_custom_target(q1 ALL DEPENDS q1.txt)
$ VERBOSE=1 make
...
"ENV_VAR=\"a b\"" echo hello > q1.txt
/bin/sh: ENV_VAR="a b": command not found
So how do you pass an environment variable with spaces? Simple.
add_custom_command(
OUTPUT q1.txt
COMMAND ${CMAKE_COMMAND} -E env ENV_VAR="a b" echo "hello" > q1.txt
VERBATIM
)
Ok, I removed my original answer as the one proposed by #Florian is better. There is one additional tweak needed for multiple quoted args. Consider a list of environment variables as such:
set(my_env_vars LDFLAGS="-Ldir1 -Ldir2" CFLAGS="-Idira -Idirb")
In order to produce the desired expansion, convert to string and then replace ; with a space.
set(my_env_string "${my_env_vars}") #produces LDFLAGS="...";CFLAGS="..."
string(REPLACE ";" " " my_env_string "${my_env_string}")
Then you can proceed with #Florian's brilliant answer and add the custom launch rule. If you need semicolons in your string then you'll need to convert them to something else first.
Note that in this case I didn't need to launch with env:
set_target_properties(mytarget PROPERTIES RULE_LAUNCH_CUSTOM "${my_env_string}")
This of course depends on your shell.
On second thought, my original answer is below as I also have a case where I don't have access to the target name.
set(my_env LDFLAGS=\"-Ldir -Ldir2" CFLAGS=\"-Idira -Idirb\")
add_custom_command(COMMAND sh -c "${my_env} grep LDFLAGS" VERBATIM)
This technique still requires that the semicolons from the list->string conversion be replaced.
Some folks suggest to use ${CMAKE_COMMAND} and pass your executable as an argument, e.g:
COMMAND ${CMAKE_COMMAND} -E env "$(WindowsSdkDir)/bin/x64/makecert.exe" ...
That worked for me.

Constructing commandline flags for an external program out of bash array

I've got an array of filenames I'd like to be ignored by stow, for example
IGNORES=('post_install\.sh' 'dummy')
(actually, that list isn't fixed but read from a file and will not always have the same length, so hardcoding like below won't work).
I form commandline flags out of the array like so
IGNORES=("${IGNORES[#]/#/--ignore=\'}")
IGNORES=("${IGNORES[#]/%/\'}")
When I do
stow -v "${IGNORES[#]}" -t $home $pkg
however, the ignores are not respected by stow, but it doesn't complain about invalid arguments either. Directly writing
stow -v --ignore='post_install\.sh' --ignore='ignore' -t $home $pkg
does work though.
What is the difference between these two ways of passing the --ignore flags, any ideas how to fix the issue? To my understanding, "${IGNORES[#]}" should evaluate to one word per array element and have the intended effect (I tried removing the quotes and indexing the array with *, too, but to no avail).
Thanks!
So while writing the post, I came across the solution: The single quotes I added here
IGNORES=("${IGNORES[#]/#/--ignore=\'}")
IGNORES=("${IGNORES[#]/%/\'}")
became part of the file names to ignore, and indeed a file named 'ignore' would be skipped; doing only
IGNORES=("${IGNORES[#]/#/--ignore=}")
has the desired effect. I still need to check how this copes with spaces in the array elements, but my guess is that it works just fine since the necessity of quoting words with spaces only stems from splitting a complete commandline into words like
stow -v --ignore='the file' -t $home $pkg
vs
stow -v --ignore=the file -t $home $pkg
which is not a problem for the above and "${IGNORES[#]}" gets the words just right.

Can I send a string to grep with commands and file names in a bash script?

Is it possible to send an array variable from the command line,
(where argsGrep="$#" and the command line input is something to the extent of -i Something) to a grep command
e.g.
result=$(grep $argsGrep ./file)
When $argsGrep has only the term to be searched, it works just fine, but the moment it contains more than the text and has a grep command, I can't get it to work whatsoever.
Don't use the intermediate string. It will just break things.
Just expand "$#" at the point you need it.
If you must save the contents of "$#" for some reason then you must use another array.
argsarr=("$#")
result=$(grep "${argsarr[#]}" ./file)

In C, what's the typical way to handle multiple arguments that are "list"-like?

Suppose I have some program called "combine" that takes input of "red", "green" and "blue"-type files to produce an output file (let's say "color.jpg")... BUT the number of each type is arbitrary. Let's also suppose that there's no way to determine what type the file is except through how the user classifies them. What do people usually do in this case?
For instance, on the command line, some of the approaches might be:
command red1,red2,red3 green1,green2 blue1 color.jpg
This comma-approach breaks down if commas can appear in the filenames. It's the approach I like the most though. Another idea would be
command "red1 red2 red3" "green1 green2" "blue1" color.jpg
but this approach also has trouble with spaces in names.
I could also require ASCII files containing lists giving the files of each type:
command redlist greenlist bluelist color.jpg
but this requires lugging around extra files.
Further ideas? Is there a standard LINUX way of doing this?
The standard way would be this:
command --red red1.jpg --red red2.jpg --blue blue1.jpg
With short options:
command -r red1.jpg -r red2.jpg -b blue1.jpg
With bash shorthand:
command -r={red1,red2}.jpg -b blue1.jpg
(The above gets expanded by the shell so it looks like the previous invocation.)
Doing things this way avoids arbitrary limitations like "no commas in filenames" and also makes your program more interoperable with standard *nix utilities like xargs and so on.
Another way is accepting:
command -r redfile1 redfile2 -b bluefile1 blue2 blue2 -g green1
so that:
command -r red* -b blue* -g green*
is possible.

Using wildcard character in bash scripting

I'm creating a bash script that will load CSV files using SQL*Loader. Please refer to the code below:
#!/bin/bash
FILENAME = '/u02/logs/$(date -d '2 days ago' +%Y-%m-%d)*.csv'
# LOAD CSV FILE USING SQL*LOADER
sqlldr username/password#localhost control=control.ctl data=$FILENAME
However, when I try to run this script, I recieved the following error: SQL*Loader-500: Unable to open file (/u02/logs/*2011-11-06*.csv). I figure out that the problem is my * wildcard which is being interpreted as a string instead of a wildcard in bash.
Is there a way to tell the bash that my asterisk (*) is a wildcard and not a string?
Thank you for your support.
i dont know about sqlldr, but i think you can try:
#!/bin/bash
FILENAME = '/u02/logs/$(date -d '2 days ago' +%Y-%m-%d)*.csv'
# LOAD CSV FILE USING SQL*LOADER
for fname in $(ls $FILENAME); do
sqlldr username/password#localhost control=control.ctl data=$fname
done
hope it helps
Your use of single ticks is the problem. Also, I'm not used to seeing bash code have spaces surrounding the equals sign. Then again, I'm old skool. So this is what I'd do:
FILENAME=/u02/logs/"$(date -d '2 days ago' +%Y-%m-%d)*.csv"
That should do it. You don't need quotes around the first part since they're literal. Only use double quotes when you need the interpreter to do do some interpolation. Use single quotes when you don't want the interpreter to touch it.
Just use
FILE=/u02/logs/$(date -d '2 days ago' +%Y-%m-%d)*.csv
I also note additional * after the logs/ in your error message, but not in your code. Adjust accordingly.
The * will stay in the filename if there is no file matching the wildcard pattern. Also, be careful where more than one matching file exists.
Maybe you can try:
sqlldr username/password#localhost control=control.ctl data="$FILENAME"

Resources