Usuń zmienną, aby użyć jej jako treści innego skryptu

20

To pytanie nie dotyczy tego, jak napisać poprawnie literał łańcucha znaków. Nie mogłem znaleźć żadnego powiązanego pytania, które nie dotyczyłoby ucieczki przed zmiennymi w celu bezpośredniego wykorzystania w skrypcie lub przez inne programy.

Moim celem jest umożliwienie skryptowi generowania innych skryptów. Wynika to z faktu, że zadania w wygenerowanych skryptach będą uruchamiane w dowolnym miejscu od 0 do n razy na innym komputerze, a dane, z których zostały wygenerowane, mogą ulec zmianie przed ich uruchomieniem (ponownie), więc wykonywanie operacji bezpośrednio, przez sieć nie działa.

Biorąc pod uwagę znaną zmienną, która może zawierać znaki specjalne, takie jak pojedyncze cudzysłowy, muszę ją zapisać jako dosłowny ciąg znaków, np. Zmienna foozawierająca bar'bazpowinna pojawić się w wygenerowanym skrypcie jako:

qux='bar'\''baz'

który zostałby napisany przez dodanie "qux=$foo_esc"do innych wierszy skryptu. Zrobiłem to przy użyciu Perla w następujący sposób:

foo_esc="'`perl -pe 's/('\'')/\\1\\\\\\1\\1/g' <<<"$foo"`'"

ale wydaje się, że to przesada.

Nie odniosłem sukcesu, robiąc to sam z bash. Próbowałem wielu odmian tych:

foo_esc="'${file//\'/\'\\\'\'}'"
foo_esc="'${file//\'/'\\''}'"

ale albo dodatkowe ukośniki pojawiają się na wyjściu (kiedy to robię echo "$foo"), albo powodują błąd składniowy (oczekując dalszych danych wejściowych, jeśli zrobione z powłoki).

Walf
źródło
aliasi / lub setdość ogólnie
mikeserv
@mikeserv Niestety nie jestem pewien, co masz na myśli.
Walf
alias "varname=$varname" varnamelubvar=value set
mikeserv
2
@mikeserv To za mało kontekstu, aby zrozumieć, co powinna zrobić twoja sugestia, ani jak użyłbym jej jako ogólnej metody ucieczki od dowolnej zmiennej. To rozwiązany problem, kolego.
Walf

Odpowiedzi:

27

Bash ma opcję rozszerzenia parametrów dla dokładnie tego przypadku :

${parameter@Q}Rozwinięcie jest łańcuchem będącym wartością parametru cytowanego w formacie, który można ponownie wykorzystać jako dane wejściowe.

Więc w tym przypadku:

foo_esc="${foo@Q}"

Jest to obsługiwane w wersji Bash 4.4 i nowszych. Istnieje kilka opcji dla innych form rozwijania, a także dla generowania kompletnych instrukcji przypisania ( @A).

Michael Homer
źródło
7
Zgrabne, ale mają tylko 4.2, co daje bad substitution.
Walf
3
Odpowiednikiem powłoki Z jest "${foo:q}".
JdeBP
Naprawdę uratuj mi życie! "${foo@Q}"Pracuje!
hao
@JdeBP, że odpowiednik powłoki Z nie działa. Jakieś inne pomysły na Zsh?
Steven Shaw
1
Znalazłem odpowiedź: „$ {(@ qq) foo}”
Steven Shaw,
11

Bash zawiera printfwbudowany %qspecyfikator formatu, który wykonuje dla ciebie ucieczkę powłoki, nawet w starszych (<4.0) wersjach Bash:

printf '[%q]\n' "Ne'er do well"
# Prints [Ne\'er\ do\ well]

printf '[%q]\n' 'Sneaky injection $( whoami ) `ls /root`'
# Prints [Sneaky\ injection\ \$\(\ whoami\ \)\ \`ls\ /root\`]

Tej sztuczki można także użyć do zwrócenia tablic danych z funkcji:

function getData()
{
  printf '%q ' "He'll say hi" 'or `whoami`' 'and then $( byebye )'
}

declare -a DATA="( $( getData ) )"
printf 'DATA: [%q]\n' "${DATA[@]}"
# Prints:
# DATA: [He\'ll\ say\ hi]
# DATA: [or\ \`whoami\`]
# DATA: [and\ then\ \$\(\ byebye\ \)]

Zauważ, że printfwbudowane Bash różni się od printfnarzędzia, które jest dostarczane w pakiecie z większością systemów operacyjnych typu Unix. Jeśli z jakiegoś powodu printfpolecenie wywołuje narzędzie zamiast wbudowanego, zawsze można wykonać builtin printfzamiast tego.

Dejay Clayton
źródło
Nie jestem pewien, jak to pomaga, gdyby to, co chciałbym wydrukować, byłoby 'Ne'\''er do well'itd., Tj. Cytaty zawarte w wydruku.
Walf
1
@Walf Myślę, że nie rozumiesz, że te dwie formy są równoważne i obie są całkowicie tak samo bezpieczne. Np. [[ 'Ne'\''er do well' == Ne\'er\ do\ well ]] && echo 'equivalent!'equivalent!
Rozbrzmiewa
Tęskniłem za tym: P, jednak wolę cytowany formularz, ponieważ łatwiej go czytać w przeglądarce / edytorze wyróżniającym składnię.
Walf
@ W połowie wydaje się, że twoje podejście jest dość niebezpieczne, biorąc pod uwagę, że w twoim przykładzie Perl, przekazanie wartości podobnej 'hello'skutkuje niepoprawną wartością ''\''hello'', która zawiera niepotrzebne początkowe puste ciągi (pierwsze dwa pojedyncze cudzysłowy) i niewłaściwy końcowy pojedynczy cytat.
Dejay Clayton
1
@Walf, dla wyjaśnienia, $'escape-these-chars'to funkcja cytowania ANSI-C w Bash, która powoduje, że wszystkie znaki w określonym ciągu są ucieczkowe. Tak więc, aby łatwo utworzyć literał łańcuchowy, który zawiera nowy wiersz w nazwie pliku (np. $'first-line\nsecond-line')Użyj \nw tej konstrukcji.
Dejay Clayton
8

Chyba nie RTFM. Można to zrobić w następujący sposób:

q_mid=\'\\\'\'
foo_esc="'${foo//\'/$q_mid}'"

Następnie echo "$foo_esc"podaje oczekiwane'bar'\''baz'


Tak naprawdę używam go z funkcją:

function esc_var {
    local mid_q=\'\\\'\'
    printf '%s' "'${1//\'/$mid_q}'"
}

...

foo_esc="`esc_var "$foo"`"

Modyfikowanie tego w celu użycia printfwbudowanego rozwiązania Dejay:

function esc_vars {
    printf '%q' "$@"
}
Walf
źródło
3
Użyłbym rozwiązania @ michael-homer, gdyby moja wersja go obsługiwała.
Walf
4

Istnieje kilka rozwiązań, aby podać wartość var:

  1. alias
    W większości powłok (gdzie alias jest dostępny) (z wyjątkiem csh, tcsh i prawdopodobnie innych csh jak):

    $ alias qux=bar\'baz
    $ alias qux
    qux='bar'\''baz'

    Tak, działa to w wielu shpodobnych powłokach, takich jak kreska lub popiół.

  2. set
    Także w większości powłok (znowu nie csh):

    $ qux=bar\'baz
    $ set | grep '^qux='
    qux='bar'\''baz'
  3. skład
    niektórych powłokach (przynajmniej ksh, bash i zsh):

    $ qux=bar\'baz
    $ typeset -p qux
    typeset qux='bar'\''baz'             # this is zsh, quoting style may
                                         # be different for other shells.
  4. eksport
    Najpierw wykonaj:

    export qux=bar\'baz

    Następnie użyj:
    export -p | grep 'qux=' export -p | grep 'qux='
    export -p qux

  5. quote
    echo "${qux@Q}"
    echo "${(qq)qux}" # od jednego do czterech q może być użyte.

Izaak
źródło
Podejście aliasowe jest sprytne i wydaje się, że jest określone przez POSIX. Myślę, że jest to najlepsza droga do maksymalnej przenośności. Wierzę sugestie udziałem grepz exportlub setmoże pęknąć na zmiennych zawierających wbudowane znaki nowej linii.
jw013