0

I am trying to execute a command like this

./build -t -c cflag="-Os -march=haswell"

The following is my script:

#!/bin/bash

set -x

ARGS=" -t"

MARCH=${MARCH:-haswell}
CFLAG="-Os"
CFLAG+=" -march=$MARCH"
ARGS+=" -c cflag=\"${CFLAG}\""

./build $ARGS

Here is build:

#!/usr/bin/env python3

import sys

for arg in sys.argv:
    print(arg)

Bash always adding extra single quotes in the command:

+ ARGS=' -t'
+ MARCH=haswell
+ CFLAG=-Os
+ CFLAG+=' -march=haswell'
+ ARGS+=' -c cflag="-Os -march=haswell"'
+ ./build -t -c 'cflag="-Os' '-march=haswell"'
./build
-t
-c
cflag="-Os
-march=haswell"

Is there a way to disable this behavior in bash?

DreamLinuxer
  • 371
  • 2
  • 9
  • Remove `set -x`? – Cyrus Jan 09 '22 at 00:27
  • I need to see how the command is executed, does `set -x` affect anything? – DreamLinuxer Jan 09 '22 at 00:30
  • It shows the quotes. They aren't really added anywhere. – choroba Jan 09 '22 at 00:36
  • @choroba It does affect the command, please check my update. – DreamLinuxer Jan 09 '22 at 00:45
  • I don't see any single quotes in the output. Please read carefully how quoting works in shells. – choroba Jan 09 '22 at 00:51
  • 1
    The real data has _literal double quotes_ in it. The single quotes are just added to show you where the argument boundaries are as a diagnostic tool; they're not part of the real command line (which is an array of C strings, not an individual string). – Charles Duffy Jan 09 '22 at 00:51
  • 2
    This is extensively covered in [BashFAQ #50](https://mywiki.wooledge.org/BashFAQ/050). – Charles Duffy Jan 09 '22 at 00:52
  • It's not about the `set -x`. The output from build shows that it does affect the arguments. – DreamLinuxer Jan 09 '22 at 00:52
  • 1
    Right. The problem is that you want your double quotes to be syntactic, but they're literal instead. That's normal and working-as-designed for very good reasons (it would be impossible to write code handling untrusted data in bash if data were silently reparsed as code). – Charles Duffy Jan 09 '22 at 00:52
  • You also have `python` involved, so this isn't exclusively a bash issue. (though the shell behavior will be) – David C. Rankin Jan 09 '22 at 00:53
  • In general, you should write your script using arrays, not strings. `args=( -t ); march=${march:-haswell}; cflags=( -Os ); cflags+=( -march="$march" ); args+=( "${cflags[@]}" ); ./build "${args[@]}"` – Charles Duffy Jan 09 '22 at 00:57
  • (note the use of lower-case variable names -- that's deliberate and per POSIX standards; see https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap08.html, keeping in mind that shell variables and environment variables share a namespace -- to honor the convention that lowercase environment variables are reserved for application use while all-uppercase ones may reflect or modify behavior of POSIX-defined tools, one needs to use lowercase names for regular shell variables, because setting an uppercase shell variable name will overwrite any like-named environment variable). – Charles Duffy Jan 09 '22 at 00:59
  • ...now, there _are_ cases where values are passed as strings rather than arrays for either compatibility or interface-limitation reasons -- f/e, environment variables are just individual C strings and cannot be arrays. In those cases, the answers on the linked duplicates are appropriate. – Charles Duffy Jan 09 '22 at 01:00
  • You code generates `./build -t -Os -march=haswell` not `./build -t -c cflag="-Os -march=haswell"`. The thing that really bugs me is the double quote in `cflag=`. – DreamLinuxer Jan 09 '22 at 01:04
  • @DreamLinuxer, `args+=( cflag="${cflags[*]}" )` if that's what you want then. Mind, that has some serious caveats of its own; if I knew that the content of `cflag=` was going to be parsed by the same shell, I would use `printf '%q ' "${cflags[@]}"` to generate the string to use there. However, in practice it's likely that it _won't in fact_ be parsed by bash (even if a shell is invoked it's likely to be `/bin/sh`), so going the route that would be the Right Thing for consumption by bash likely the wrong thing in the real-world scenario. – Charles Duffy Jan 09 '22 at 01:05
  • @DreamLinuxer, ...note that the double quote being literal _is the whole problem_. If it were correctly syntactic, you would get `'cflag=-Os -march=haswell'` emitted by `set -x` (in some versions of bash; the details are version-defined -- think of `set -x` like using Python's `repr()` to print results, where a single string can be written out several possible ways) – Charles Duffy Jan 09 '22 at 01:05
  • 1
    ...and just to be clear, `'cflag=-Os -march=haswell'` and `cflag="-Os -march=haswell"` and `cflag=-Os\ -march=haswell` (and, for that matter, `cflag=-Os" "-march=haswell`) are different ways of representing _the exact same string_ in bash; which one of those representations `set -x` happens to use is immaterial. – Charles Duffy Jan 09 '22 at 01:09
  • Now it becomes `./build -t 'cflag=-Os -march=haswell'`, the double quote is missing. The output from build becomes `['./build', '-t', 'cflag=-Os -march=haswell']`, but I need it to be `['./build', '-t', 'cflag="-Os -march=haswell"']`. – DreamLinuxer Jan 09 '22 at 01:10
  • If you would run `./build -t cflag="-Os -march=haswell"` at a shell, **the double quotes are syntactic, not literal**. They're gone before `python` is started. See what I said above about there being lots of different ways to write the same string. – Charles Duffy Jan 09 '22 at 01:10
  • Try it -- check your `sys.argv` between both `./build -t cflag="-Os -march=haswell"` and `./build -t 'cflag=-Os -march=haswell'`; you'll see it's identical. – Charles Duffy Jan 09 '22 at 01:11
  • I see, so they will disappear no matter how I pass the arguments. Thanks. – DreamLinuxer Jan 09 '22 at 01:12
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/240878/discussion-between-charles-duffy-and-dreamlinuxer). – Charles Duffy Jan 09 '22 at 01:12
  • Since you are using bash, you want to use a shell array for this -- use `ARGS=("-t")` to initially set the array to a single value, then `ARGS+=("-c" "cflag=$CFLAG")` to add two more args to it, then `./build "${args[@]}"` to call build with those arguments (and without resplitting them, so the space in cflag will remain untouched.) – Chris Dodd Jan 09 '22 at 01:53

0 Answers0