Deaktivierte schreibgeschützte Variable in Bash

78

Wie deaktiviere ich eine schreibgeschützte Variable in Bash?

$ readonly PI=3.14

$ unset PI
bash: PI: readonly variable

oder ist es nicht möglich

Kokizzu
quelle
ah mein schlechtes tldp.org/LDP/Bash-Beginners-Guide/html/sect_10_01.html Variablen schreibgeschützt machen. Diesen Variablen können dann weder durch nachfolgende Zuweisungsanweisungen Werte zugewiesen noch sie können nicht gesetzt werden.
Kokizzu
Normalerweise sind die Variablen schreibgeschützt, da / etc / profile viele Zeilen wie diese enthält readonly TMOUT. Ich ziehe es vor, diese Zeilen zu kommentieren und eine neue Verbindung zu diesem Linux-Computer herzustellen.
ROMANIA_engineer
1
@ ROMANIA_engineer Oder führen Sie einfach bash --norc aus und legen Sie dann das gewünschte Material manuell oder in Ihrer eigenen RC-Datei fest - zB: source ~ / .gnbashrc
Graham Nicholls

Antworten:

108

Tatsächlich können Sie eine schreibgeschützte Variable deaktivieren . aber ich muss warnen, dass dies eine hackige Methode ist. Hinzufügen dieser Antwort nur als Information, nicht als Empfehlung. Verwenden Sie es auf eigenes Risiko. Getestet auf Ubuntu 13.04, Bash 4.2.45.

Diese Methode beinhaltet das Kennen von Bash-Quellcode und er wird von dieser Antwort geerbt .

$ readonly PI=3.14
$ unset PI
-bash: unset: PI: cannot unset: readonly variable
$ cat << EOF| sudo gdb
attach $$
call unbind_variable("PI")
detach
EOF
$ echo $PI

$

Eine Oneliner-Antwort besteht darin, den Batch-Modus und andere Befehlszeilen-Flags zu verwenden, wie in der Antwort von F. Hauri angegeben :

$ sudo gdb -ex 'call unbind_variable("PI")' --pid=$$ --batch

sudoMöglicherweise wird es basierend auf den ptrace_scope-Einstellungen Ihres Kernels benötigt oder nicht. Weitere Informationen finden Sie in den Kommentaren zur Antwort von vip9937.

anishsane
quelle
53
Nun, das würde ich Redneck-Bash-Programmierung nennen;)
Floyd
5
Hinweis: nicht ändern verlockt cat << EOF| sudo gdbzu sudo gdb << EOF. Es kann nicht funktionieren, da die umgeleiteten Eingang Anbieter - bashwird aufgrund gestoppt gdbBefestigung.
Anishsane
Ich denke, diese Antwort ist etwas genauer, da sie kein
Sudo
1
^^ EOF bei stdin & explizites Beenden würde beide gdb sauber beenden.
Anishsane
2
Ich mag einen Liner:echo -e "attach $$\n call unbind_variable(\"PI\")\n detach" | gdb
Satya Mishra
48

Ich habe den obigen GDB-Hack ausprobiert, weil ich TMOUT deaktivieren möchte (um die automatische Abmeldung zu deaktivieren), aber auf dem Computer, auf dem TMOUT als schreibgeschützt festgelegt ist, darf ich sudo nicht verwenden. Aber da ich den Bash-Prozess besitze, brauche ich kein Sudo. Die Syntax funktionierte jedoch nicht ganz mit der Maschine, auf der ich mich befinde.

Dies hat jedoch funktioniert (ich habe es in meine .bashrc-Datei eingefügt):

# Disable the stupid auto-logout
unset TMOUT > /dev/null 2>&1
if [ $? -ne 0 ]; then
    gdb <<EOF > /dev/null 2>&1
 attach $$
 call unbind_variable("TMOUT")
 detach
 quit
EOF
fi
vip9937
quelle
3
Ich würde vorschlagen, -q -nOptionen zu verwenden, um gdbeine nicht geladene .gdbinit- Datei pro Sicherheit zum Schweigen zu bringen.
Lucas Cimon
3
"Da ich den Bash-Prozess besitze, brauche ich kein Sudo." Beachten Sie, dass dies davon abhängt, welches Betriebssystem Sie verwenden und wie es konfiguriert ist. Bei den meisten derzeit verwendeten Versionen des Linux-Kernels wird dies über gesteuert/proc/sys/kernel/yama/ptrace_scope . Die gebräuchlichsten Werte sind 0, in welchem ​​Fall Sie dies tun können und 1in welchem ​​Fall Sie dies wahrscheinlich nicht können, da dies gdbnicht das direkte übergeordnete bashElement des zu debuggenden Prozesses ist .
Eliah Kagan
Obwohl -qund -nsind hilfreich, sie (nämlich -q) darf nicht stummgeschaltet gdb, so dass die /dev/nullUmleitung nach wie vor erforderlich ist. Toller Vorschlag, @LucasCimon
fbicknel
Irgendwelche Ideen, wie man etwas Ähnliches auf einer Maschine ohne GDB macht?
Lichtschalter05
1
@ Lightswitch05: siehe meine ctypes.sh Antwort
Wil
6

Laut Manpage:

   unset [-fv] [name ...]
          ...   Read-only  variables  may  not  be
          unset. ...

Wenn Sie die Variable noch nicht exportiert haben, können Sie sie exec "$0" "$@"zum Neustart Ihrer Shell verwenden. Natürlich verlieren Sie auch alle anderen nicht exportierten Variablen. Wenn Sie eine neue Shell ohne starten exec, verliert sie anscheinend ihre schreibgeschützte Eigenschaft für diese Shell.

Kevin
quelle
6

Die Verwendung von GDB ist furchtbar langsam. Versuchen Sie stattdessen ctypes.sh. Es funktioniert mit libffi, um stattdessen die unbind_variable () von bash direkt aufzurufen. Dies ist genauso schnell wie bei jeder anderen eingebauten bash:

$ readonly PI=3.14
$ unset PI
bash: unset: PI: cannot unset: readonly variable

$ source ctypes.sh
$ dlcall unbind_variable string:PI

$ declare -p PI
bash: declare: PI: not found

Zuerst müssen Sie ctypes.sh installieren:

$ git clone https://github.com/taviso/ctypes.sh.git
$ cd ctypes.sh
$ ./autogen.sh
$ ./configure
$ make
$ sudo make install

Eine vollständige Beschreibung und Dokumente finden Sie unter https://github.com/taviso/ctypes.sh .

Für Neugierige können Sie auf diese Weise jede Funktion innerhalb von bash oder jede Funktion in jeder mit bash verknüpften Bibliothek oder sogar jede externe dynamisch geladene Bibliothek aufrufen, wenn Sie möchten. Bash ist jetzt genauso gefährlich wie Perl ... ;-)

Wil
quelle
1
Ich gehe davon aus, wo du sagst, include ctypes.shdu meinst source ctypes.shoder . ctypes.sh.
Dennis Williamson
5

Kurz: inspiriert von Anishsanes Antwort

Aber mit einfacherer Syntax:

gdb -ex 'call unbind_variable("PI")' --pid=$$ --batch

Mit einigen Verbesserungen als Funktion:

Meine destroyFunktion:

Oder wie man mit variablen Metadaten spielt . Beachten Sie die Verwendung seltener Bashismen : local -n VARIABLE=$1und ${VARIABLE@a}...

destroy () { 
    local -n variable=$1
    declare -p $1 &>/dev/null || return -1 # Return if variable not exist
    local reslne result flags=${variable@a}
    [ -z "$flags" ] || [ "${flags//*r*}" ] && { 
        unset $1    # Don't run gdb if variable is not readonly.
        return $?
    }
    while read resline; do
        [ "$resline" ] && [ -z "${resline%\$1 = *}" ] &&
            result=${resline##*1 = }
    done < <(
        gdb 2>&1 -ex 'call unbind_variable("'$1'")' --pid=$$ --batch
    )
    return $result
}

Sie könnte dies zu einem kopiert bash Quelldatei genannt destroy.bash, für die Probe ...

Erläuterung:

 1  destroy () { 
 2      local -n variable=$1
 3      declare -p $1 &>/dev/null || return -1 # Return if variable not exist
 4      local reslne result flags=${variable@a}
 5      [ -z "$flags" ] || [ "${flags//*r*}" ] && { 
 6          unset $1    # Don't run gdb if variable is not readonly.
 7          return $?
 8      }
 9      while read resline; do
10          [ "$resline" ] && [ -z "${resline%\$1 = *}" ] &&
11                result=${resline##*1 = }
12      done < <(
13          gdb 2>&1 -ex 'call unbind_variable("'$1'")' --pid=$$ --batch
14      )
15      return $result
16  }
  • Zeile 2 erstellt einen lokalen Verweis auf die übermittelte Variable.
  • Zeile 3 verhindert das Ausführen einer nicht vorhandenen Variablen
  • Zeile 4 speichert die Parameterattribute (Meta) in $flags.
  • Die Zeilen 5 bis 8 werden ausgeführt, unsetanstatt gdbwenn kein schreibgeschütztes Flag vorhanden ist
  • Die Zeilen 9 bis 12 while read ... result= ... doneerhalten den Rückkehrcode call unbindin der gdbAusgabe
  • Zeile 13 gdbSyntax mit Verwendung von --pidund --ex(siehe gdb --help).
  • 15 Rücklaufleitung $resultdes call unbindBefehls.

In Benutzung:

source destroy.bash 

# 1st with any regular (read-write) variable: 
declare PI=$(bc -l <<<'4*a(1)')
echo $PI
3.14159265358979323844
echo ${PI@a} # flags

declare -p PI
declare -- PI="3.14159265358979323844"
destroy PI
echo $?
0
declare -p PI
bash: declare: PI: not found

# now with read only variable:
declare -r PI=$(bc -l <<<'4*a(1)')
declare -p PI
declare -r PI="3.14159265358979323844"
echo ${PI@a} # flags
r
unset PI
bash: unset: PI: cannot unset: readonly variable

destroy PI
echo $?
0
declare -p PI
bash: declare: PI: not found

# and with non existant variable
destroy PI
echo $?
255
F. Hauri
quelle
5

In zsh,

% typeset +r PI
% unset PI

(Ja, ich weiß, dass die Frage Bash lautet. Wenn Sie jedoch nach zsh googeln, erhalten Sie auch eine Reihe von Bash-Fragen.)

Radon Rosborough
quelle
Funktioniert! Weitere Informationen: zsh.sourceforge.net/Doc/Release/Shell-Builtin-Commands.html . Vielen Dank!
Wellington1993
4

Speziell für die TMOUT-Variable geschrieben. Eine andere Option, wenn gdb nicht verfügbar ist, besteht darin, bash in Ihr Home-Verzeichnis zu kopieren und die TMOUT-Zeichenfolge in der Binärdatei auf etwas anderes zu patchen, z. B. XMOUX. Führen Sie dann diese zusätzliche Shell-Schicht aus, und Sie erhalten keine Zeitüberschreitung.

user1089933
quelle
2
Noch böser als der GDB-Hack. Also ... +1!
Alois Mahdal
2

Der Befehl readonly macht ihn endgültig und dauerhaft, bis der Shell-Prozess beendet wird. Wenn Sie eine Variable ändern müssen, markieren Sie sie nicht schreibgeschützt.

Amit Verma
quelle
2

Nein, nicht in der aktuellen Shell. Wenn Sie ihm einen neuen Wert zuweisen möchten, müssen Sie eine neue Shell geben, die eine neue Bedeutung hat und nicht als solche betrachtet wird read only.

$ { ( readonly pi=3.14; echo $pi ); pi=400; echo $pi; unset pi; echo [$pi]; }
3.14
400
[]
jaypal singh
quelle
2
$ PI=3.17
$ export PI
$ readonly PI
$ echo $PI
3.17
$ PI=3.14
-bash: PI: readonly variable
$ echo $PI
3.17

Was nun?

$ exec $BASH
$ echo $PI
3.17
$ PI=3.14
$ echo $PI
3.14
$

Eine Subshell kann die Variablen der Eltern erben, erbt jedoch nicht deren geschützten Status.

jezzaaaa
quelle
2

Eine Alternative, wenn gdb nicht verfügbar ist: Mit dem enableBefehl können Sie eine benutzerdefinierte integrierte Funktion laden , mit der Sie das schreibgeschützte Attribut deaktivieren können . Der Kern des Codes, der es tut:

SETVARATTR (find_variable ("TMOUT"), att_readonly, 1);

Offensichtlich würden Sie durch TMOUTdie Variable ersetzen, die Sie interessiert.

Wenn Sie das nicht selbst in ein eingebautes eingebautes Gerät verwandeln möchten, habe ich bash in GitHub gegabelt und ein vollständig geschriebenes und kompilierbares ladbares eingebautes System namens hinzugefügt readwrite. Das Commit befindet sich unter https://github.com/josephcsible/bash/commit/bcec716f4ca958e9c55a976050947d2327bcc195 . Wenn Sie es verwenden möchten, rufen Sie die Bash-Quelle mit meinem Commit ab, führen Sie sie aus ./configure && make loadables, um sie zu erstellen, enable -f examples/loadables/readwrite readwritefügen Sie sie Ihrer laufenden Sitzung hinzu und readwrite TMOUTverwenden Sie sie dann.

Joseph Sible-Reinstate Monica
quelle
1

Sie können nicht, von der Handbuchseite von unset:

Entfernen Sie für jeden Namen die entsprechende Variable oder Funktion. Wenn keine Optionen angegeben sind oder die Option -v angegeben ist, bezieht sich jeder Name auf eine Shell-Variable. Schreibgeschützte Variablen dürfen nicht deaktiviert werden. Wenn -f angegeben wird, bezieht sich jeder Name auf eine Shell-Funktion, und die Funktionsdefinition wird entfernt. Jede nicht gesetzte Variable oder Funktion wird aus der Umgebung entfernt, die an nachfolgende Befehle übergeben wird. Wenn RANDOM, SECONDS, LINENO, HISTCMD, FUNCNAME, GROUPS oder DIRSTACK deaktiviert sind, verlieren sie ihre besonderen Eigenschaften, auch wenn sie anschließend zurückgesetzt werden. Der Exit-Status ist true, es sei denn, ein Name ist schreibgeschützt.

Yu Hao
quelle
2
Was ich nicht verstehe ist, warum typeset +r VARes seitdem nicht funktioniert, auch laut Manpage,Using '+' instead of '-' turns off the attribute instead, with the exception that +a may not be used to destroy an array variable.
Trebor Rude
1

Eine andere Möglichkeit, eine schreibgeschützte Variable in Bash zu "deaktivieren", besteht darin, diese Variable in einem verfügbaren Kontext als schreibgeschützt zu deklarieren:

foo(){ declare -r PI=3.14; baz; }
bar(){ local PI=3.14; baz; }

baz(){ PI=3.1415927; echo PI=$PI; }

foo;

bash: PI: schreibgeschützte Variable

bar; 

PI = 3,1415927

Während dies innerhalb des Geltungsbereichs nicht "störend" ist, was wahrscheinlich die Absicht des ursprünglichen Autors ist, setzt dies definitiv eine schreibgeschützte Variable aus der Sicht von baz () und macht sie später aus der Sicht von schreibgeschützt In Anbetracht von baz () müssen Sie Ihr Skript nur mit Bedacht schreiben.

Wil
quelle
1

Eine andere Lösung ohne GDB oder eine externe Binärdatei (in der Tat ein Schwerpunkt auf Graham Nicholls Kommentar) wäre die Verwendung von exec.

In meinem Fall wurde eine nervige schreibgeschützte Variable eingestellt /etc/profile.d/xxx.

Zitieren des Bash-Handbuchs:

"Wenn bash als interaktive [...] Anmeldeshell aufgerufen wird, werden zuerst Befehle aus der Datei / etc / profile gelesen und ausgeführt" [...]

Wenn eine interaktive Shell gestartet wird, die keine Anmeldeshell ist, liest bash Befehle aus /etc/bash.bashrc und führt sie aus [...]

Der Kern meiner Problemumgehung bestand darin, Folgendes einzugeben ~/.bash_profile:

if [ -n "$annoying_variable" ]
then exec env annoying_variable='' /bin/bash
# or: then exec env -i /bin/bash
fi

Warnung: Um eine Rekursion zu vermeiden (die Sie sperren würde, wenn Sie nur über SSH auf Ihr Konto zugreifen können), sollten Sie sicherstellen, dass die "nervige Variable" nicht automatisch vom bashrc festgelegt wird, oder beispielsweise eine andere Variable für die Prüfung festlegen ::

if [ -n "$annoying_variable" ] && [ "${SHLVL:-1}" = 1 ]
then exec env annoying_variable='' SHLVL=$((SHLVL+1)) ${SHELL:-/bin/bash}
fi
bufh
quelle