用于从Python修改Windows环境变量的接口

时间:2009-07-06 07:48:41

标签: python windows environment-variables

如何从Python脚本中持久修改Windows环境变量? (这是setup.py脚本)

我正在寻找用于此目的的标准功能或模块。我已经熟悉了registry way of doing it,但对此也有任何意见。

6 个答案:

答案 0 :(得分:20)

使用setx有一些缺点,特别是如果你试图附加到环境变量(例如.setx PATH%Path%; C:\ mypath)这将在每次运行时重复附加到路径,这可能是一个问题。更糟糕的是,它不区分机器路径(存储在HKEY_LOCAL_MACHINE中)和用户路径(存储在HKEY_CURRENT_USER中)。您在命令提示符下看到的环境变量由这两个值的串联组成。因此,在调用setx之​​前:

user PATH == u
machine PATH == m
%PATH% == m;u

> setx PATH %PATH%;new

Calling setx sets the USER path by default, hence now:
user PATH == m;u;new
machine PATH == m
%PATH% == m;m;u;new

每次调用setx追加到PATH时,系统路径都不可避免地在%PATH%环境变量中重复。这些更改是永久性的,永远不会通过重新启动重置,因此会在机器的整个生命周期中累积。

试图在DOS中弥补这一点超出了我的能力。所以我转向Python。我今天提出的解决方案,通过调整注册表来设置环境变量,包括在不引入重复项的情况下附加到PATH,如下所示:

from os import system, environ
import win32con
from win32gui import SendMessage
from _winreg import (
    CloseKey, OpenKey, QueryValueEx, SetValueEx,
    HKEY_CURRENT_USER, HKEY_LOCAL_MACHINE,
    KEY_ALL_ACCESS, KEY_READ, REG_EXPAND_SZ, REG_SZ
)

def env_keys(user=True):
    if user:
        root = HKEY_CURRENT_USER
        subkey = 'Environment'
    else:
        root = HKEY_LOCAL_MACHINE
        subkey = r'SYSTEM\CurrentControlSet\Control\Session Manager\Environment'
    return root, subkey


def get_env(name, user=True):
    root, subkey = env_keys(user)
    key = OpenKey(root, subkey, 0, KEY_READ)
    try:
        value, _ = QueryValueEx(key, name)
    except WindowsError:
        return ''
    return value


def set_env(name, value):
    key = OpenKey(HKEY_CURRENT_USER, 'Environment', 0, KEY_ALL_ACCESS)
    SetValueEx(key, name, 0, REG_EXPAND_SZ, value)
    CloseKey(key)
    SendMessage(
        win32con.HWND_BROADCAST, win32con.WM_SETTINGCHANGE, 0, 'Environment')


def remove(paths, value):
    while value in paths:
        paths.remove(value)


def unique(paths):
    unique = []
    for value in paths:
        if value not in unique:
            unique.append(value)
    return unique


def prepend_env(name, values):
    for value in values:
        paths = get_env(name).split(';')
        remove(paths, '')
        paths = unique(paths)
        remove(paths, value)
        paths.insert(0, value)
        set_env(name, ';'.join(paths))


def prepend_env_pathext(values):
    prepend_env('PathExt_User', values)
    pathext = ';'.join([
        get_env('PathExt_User'),
        get_env('PathExt', user=False)
    ])
    set_env('PathExt', pathext)



set_env('Home', '%HomeDrive%%HomePath%')
set_env('Docs', '%HomeDrive%%HomePath%\docs')
set_env('Prompt', '$P$_$G$S')

prepend_env('Path', [
    r'%SystemDrive%\cygwin\bin', # Add cygwin binaries to path
    r'%HomeDrive%%HomePath%\bin', # shortcuts and 'pass-through' bat files
    r'%HomeDrive%%HomePath%\docs\bin\mswin', # copies of standalone executables
])

# allow running of these filetypes without having to type the extension
prepend_env_pathext(['.lnk', '.exe.lnk', '.py'])

它不会影响当前进程或父shell,但它会影响运行后打开的所有cmd窗口,无需重新启动,并且可以安全地编辑和重新运行多次,而不会引入任何重复项。< / p>

答案 1 :(得分:4)

使用外部Windows setx命令可能同样容易:

C:\>set NEWVAR
Environment variable NEWVAR not defined

C:\>python
Python 2.5.4 (r254:67916, Dec 23 2008, 15:10:54) [MSC v.1310 32 bit (Intel)] on
win32
Type "help", "copyright", "credits" or "license" for more information.
>>> import os
>>> os.system('setx NEWVAR newvalue')
0
>>> os.getenv('NEWVAR')
>>> ^Z


C:\>set NEWVAR
Environment variable NEWVAR not defined

现在打开一个新的命令提示符:

C:\>set NEWVAR
NEWVAR=newvalue

如您所见,setx既没有为当前会话设置变量,也没有为父进程(第一个命令提示符)设置变量。但它确实在注册表中持久设置变量以用于将来的过程。

我认为根本没有办法改变父进程的环境(如果有的话,我很乐意听到它!)。

答案 2 :(得分:3)

我必须在一千年前尝试通过一个程序来改变当前DOS会话的环境。问题是:该程序在自己的DOS shell中运行,因此必须在其父环境中运行。它需要一个步行,从DOS信息块开始,一直到内存控制块链,找到该父环境的位置。一旦我发现了如何做到这一点,我对操纵环境变量的需求就消失了。我将在下面给你Turbo Pascal代码,但我想至少有三种更好的方法可以解决这个问题:

  1. 创建一个批处理文件:(a)调用生成包含相应SET命令的临时批处理文件的Python脚本(或其他); (b)调用临时批处理文件(SET命令在当前shell中执行);和(c)删除临时批处理文件。

  2. 创建一个Python脚本,将“VAR1 = val1 \ nVAR2 = val2 \ nVAR3 = val3 \ n”之类的内容写入STDOUT。在批处理文件中以这种方式使用它:

    for /f "delims=|" %%X in (' callYourPythonScript ') do set %%X

    etvoilà:变量VAR1,VAR2和VAR3已赋值。

  3. 修改Windows注册表并按照Alexander Prokofyev所描述的here广播设置更改。

  4. 这里有一个只报告内存位置的程序的Pascal代码(你可能需要一本荷兰语词典和一本Pascal编程书)。它仍然可以在Windows XP下运行,无论是报告我们运行的是DOS 5.00。这只是第一次开始,为了操纵选定的环境,需要做很多低级编程。由于指针结构可能看似正确,我不太确定1994年的环境模型是否仍然存在......

    program MCBKETEN;
    uses dos, HexConv;
    
    {----------------------------------------------------------------------------}
    {  Programma: MCBKETEN.EXE                                                   }
    {  Broncode : MCBKETEN.PAS                                                   }
    {  Doel     : Tocht langs de MCB's met rapportage                            }
    {  Datum    : 11 januari 1994                                                }
    {  Auteur   : Meindert Meindertsma                                           }
    {  Versie   : 1.00                                                           }
    {----------------------------------------------------------------------------}
    
    type
       MCB_Ptr     = ^MCB;
    {  MCB_PtrPtr  = ^MCB_Ptr;  vervallen wegens DOS 2.11 -- zie verderop }
       MCB         = record
                        Signatuur    : char;
                        Eigenaar     : word;
                        Paragrafen   : word;
                        Gereserveerd : array[1..3] of byte;
                        Naam         : array[1..8] of char;
                     end;
       BlokPtr     = ^BlokRec;
       BlokRec     = record
                        Vorige       : BlokPtr;
                        DitSegment,
                        Paragrafen   : word;
                        Signatuur    : string[6];
                        Eigenaar,
                        Omgeving     : word;
                        Functie      : String4;
                        Oorsprong,
                        Pijl         : char;
                        KorteNaam    : string[8];
                        LangeNaam    : string;
                        Volgende     : BlokPtr;
                     end;
       PSP_Ptr     = ^PSP;
       PSP         = record
                        Vulsel1      : array[1..44] of byte;
                        Omgeving     : word;
                        Vulsel2      : array[47..256] of byte;
                     end;
    
    var
       Zone                  : string[5];
       ProgGevonden,
       EindeKeten,
       Dos3punt2             : boolean;
       Regs                  : registers;
       ActMCB                : MCB_Ptr;
       EersteSchakel, Schakel,
       LaatsteSchakel        : BlokPtr;
       ActPSP                : PSP_Ptr;
       EersteProg,
       Meester, Ouder,
       TerugkeerSegment,
       TerugkeerOffset,
       TerugkeerSegment2,
       OuderSegment          : word;
       Specificatie          : string[8];
       ReleaseNummer         : string[2];
       i                     : byte;
    
    
    {----------------------------------------------------------------------------}
    {  PROCEDURES EN FUNCTIES                                                    }
    {----------------------------------------------------------------------------}
    
    function Coda (Omgeving : word; Paragrafen : word) : string;
    
    var
       i            : longint;
       Vorige, Deze : char;
       Streng       : string;
    
    begin
       i    := 0;
       Deze := #0;
       repeat
          Vorige := Deze;
          Deze   := char (ptr (Omgeving, i)^);
          inc (i);
       until ((Vorige = #0) and (Deze = #0)) or (i div $10 >= Paragrafen);
       if (i + 3) div $10 < Paragrafen then begin
          Vorige := char (ptr (Omgeving, i)^);
          inc (i);
          Deze   := char (ptr (Omgeving, i)^);
          inc (i);
          if (Vorige = #01) and (Deze = #0) then begin
             Streng := '';
             Deze   := char (ptr (Omgeving, i)^);
             inc (i);
             while (Deze <> #0) and (i div $10 < Paragrafen) do begin
                Streng := Streng + Deze;
                Deze   := char (ptr (Omgeving, i)^);
                inc (i);
             end;
             Coda := Streng;
          end
          else Coda := '';
       end
       else Coda := '';
    end {Coda};
    
    
    {----------------------------------------------------------------------------}
    {  HOOFDPROGRAMMA                                                            }
    {----------------------------------------------------------------------------}
    
    BEGIN
      {----- Initiatie -----}
       Zone            := 'Lower';
       ProgGevonden    := FALSE;
       EindeKeten      := FALSE;
       Dos3punt2       := (dosversion >= $1403) and (dosversion <= $1D03);
       Meester         := $0000;
       Ouder           := $0000;
       Specificatie[0] := #8;
       str (hi (dosversion) : 2, ReleaseNummer);
       if ReleaseNummer[1] = ' ' then ReleaseNummer[1] := '0';
    
      {----- Pointer naar eerste MCB ophalen ------}
       Regs.AH := $52;  { functie $52 geeft adres van DOS Info Block in ES:BX }
       msdos (Regs);
    {  ActMCB := MCB_PtrPtr (ptr (Regs.ES, Regs.BX - 4))^;  NIET onder DOS 2.11  }
       ActMCB := ptr (word (ptr (Regs.ES, Regs.BX - 2)^), $0000);
    
      {----- MCB-keten doorlopen -----}
       new (EersteSchakel);
       EersteSchakel^.Vorige := nil;
       Schakel               := EersteSchakel;
       repeat
          with Schakel^ do begin
             DitSegment := seg (ActMCB^);
             Paragrafen := ActMCB^.Paragrafen;
             if DitSegment + Paragrafen >= $A000 then
                Zone    := 'Upper';
             Signatuur  := Zone + ActMCB^.Signatuur;
             Eigenaar   := ActMCB^.Eigenaar;
             ActPSP     := ptr (Eigenaar, 0);
             if not ProgGevonden then EersteProg := DitSegment + 1;
             if Eigenaar >= EersteProg
                then Omgeving := ActPSP^.Omgeving
                else Omgeving := 0;
             if DitSegment + 1 = Eigenaar then begin
                ProgGevonden  := TRUE;
                Functie       := 'Prog';
                KorteNaam[0]  := #0;
                while (ActMCB^.Naam[ ord (KorteNaam[0]) + 1 ] <> #0) and
                      (KorteNaam[0] < #8) do
                begin
                   inc (KorteNaam[0]);
                   KorteNaam[ ord (KorteNaam[0]) ] :=
                      ActMCB^.Naam[ ord (KorteNaam[0]) ];
                end;
                if Eigenaar = prefixseg then begin
                   TerugkeerSegment := word (ptr (prefixseg, $000C)^);
                   TerugkeerOffset  := word (ptr (prefixseg, $000A)^);
                   LangeNaam        := '-----> Terminate Vector = '     +
                                       WordHex (TerugkeerSegment) + ':' +
                                       WordHex (TerugkeerOffset )        ;
                end
                else
                   LangeNaam := '';
             end {if ÆProgØ}
             else begin
                if Eigenaar = $0008 then begin
                   if ActMCB^.Naam[1] = 'S' then
                      case ActMCB^.Naam[2] of
                         'D' : Functie := 'SysD';
                         'C' : Functie := 'SysP';
                         else  Functie := 'Data';
                      end {case}
                   else        Functie := 'Data';
                   KorteNaam := '';
                   LangeNaam := '';
                end {if Eigenaar = $0008}
                else begin
                   if DitSegment + 1 = Omgeving then begin
                      Functie   := 'Env ';
                      LangeNaam := Coda (Omgeving, Paragrafen);
                      if EersteProg = Eigenaar then Meester := Omgeving;
                   end {if ÆEnvØ}
                   else begin
                      move (ptr (DitSegment + 1, 0)^, Specificatie[1], 8);
                      if (Specificatie = 'PATH=' + #0 + 'CO') or
                         (Specificatie = 'COMSPEC='         ) or
                         (Specificatie = 'OS=DRDOS'         ) then
                      begin
                         Functie   := 'Env' + chr (39);
                         LangeNaam := Coda (DitSegment + 1, Paragrafen);
                         if (EersteProg = Eigenaar) and
                            (Meester    = $0000   )
                         then
                            Meester := DitSegment + 1;
                      end
                      else begin
                         if Eigenaar = 0
                            then Functie := 'Free'
                            else Functie := 'Data';
                         LangeNaam := '';
                         if (EersteProg = Eigenaar) and
                            (Meester    = $0000   )
                         then
                            Meester := DitSegment + 1;
                      end;
                   end {else: not ÆEnvØ};
                   KorteNaam := '';
                end {else: Eigenaar <> $0008};
             end {else: not ÆProgØ};
    
            {----- KorteNaam redigeren -----}
             for i := 1 to length (KorteNaam) do
                if KorteNaam[i] < #32 then KorteNaam[i] := '.';
             KorteNaam := KorteNaam + '        ';
    
            {----- Oorsprong vaststellen -----}
             if EersteProg = Eigenaar
                then Oorsprong := '*'
                else Oorsprong := ' ';
    
            {----- Actueel proces (uitgaande Pijl) vaststellen -----}
             if Eigenaar = prefixseg
                then Pijl := '>'
                else Pijl := ' ';
          end {with Schakel^};
    
         {----- MCB-opeenvolging onderzoeken / schakelverloop vaststellen -----}
          if (Zone = 'Upper') and (ActMCB^.Signatuur = 'Z') then begin
             Schakel^.Volgende := nil;
             EindeKeten        := TRUE;
          end
          else begin
             ActMCB := ptr (seg (ActMCB^) + ActMCB^.Paragrafen + 1, 0);
             if ((ActMCB^.Signatuur <> 'M') and (ActMCB^.Signatuur <> 'Z')) or
                ($FFFF - ActMCB^.Paragrafen < seg (ActMCB^)               )
             then begin
                Schakel^.Volgende := nil;
                EindeKeten        := TRUE;
             end
             else begin
                new (LaatsteSchakel);
                Schakel^.Volgende      := LaatsteSchakel;
                LaatsteSchakel^.Vorige := Schakel;
                Schakel                := LaatsteSchakel;
             end {else: (ÆMØ or ÆZØ) and Æteveel_ParagrafenØ};
          end {else: ÆLowerØ or not ÆZØ};
       until EindeKeten;
    
      {----- Terugtocht -----}
       while Schakel <> nil do with Schakel^ do begin
    
         {----- Ouder-proces vaststellen -----}
          TerugkeerSegment2 := TerugkeerSegment + (TerugkeerOffset div $10);
          if (DitSegment              <= TerugkeerSegment2) and
             (DitSegment + Paragrafen >= TerugkeerSegment2)
          then
             OuderSegment := Eigenaar;
    
         {----- Meester-omgeving markeren -----}
          if DitSegment + 1 = Meester then Oorsprong := 'M';
    
         {----- Schakel-verloop -----}
          Schakel := Schakel^.Vorige;
       end {while Schakel <> nil};
    
      {----- Rapportage -----}
       writeln ('Chain of Memory Control Blocks in DOS version ',
                lo (dosversion), '.', ReleaseNummer, ':');
       writeln;
       writeln ('MCB@ #Par Signat PSP@ Env@ Type !! Name     File');
       writeln ('---- ---- ------ ---- ---- ---- -- -------- ',
                '-----------------------------------');
       Schakel := EersteSchakel;
       while Schakel <> nil do with Schakel^ do begin
    
         {----- Ouder-omgeving vaststellen -----}
          if Eigenaar = OuderSegment then begin
             if not Dos3punt2 then begin
                if (Functie = 'Env ') then begin
                   Ouder := DitSegment + 1;
                   Pijl  := 'Û';
                end
                else
                   Pijl := '<';
             end {if not Dos3punt2}
             else begin
                if ((Functie = 'Env' + chr (39)) or (Functie = 'Data')) and
                   (Ouder    = $0000)
                then begin
                   Ouder := DitSegment + 1;
                   Pijl  := 'Û';
                end
                else
                   Pijl := '<';
             end {else: Dos3punt2};
          end {with Schakel^};
    
         {----- Keten-weergave -----}
          writeln (WordHex (DitSegment)        , ' ',
                   WordHex (Paragrafen)        , ' ',
                   Signatuur                   , ' ',
                   WordHex (Eigenaar)          , ' ',
                   WordHex (Omgeving)          , ' ',
                   Functie                     , ' ',
                   Oorsprong, Pijl             , ' ',
                   KorteNaam                   , ' ',
                   LangeNaam                        );
    
         {----- Schakel-verloop -----}
          Schakel := Schakel^.Volgende;
       end {while Schakel <> nil};
    
      {----- Afsluiting rapportage -----}
       writeln;
    
       write ('* = First command interpreter at ');
       if ProgGevonden
          then writeln (WordHex (EersteProg), ':0000')
          else writeln ('?');
    
       write ('M = Master environment        at ');
       if Meester > $0000
          then writeln (WordHex (Meester), ':0000')
          else writeln ('?');
    
       write ('< = Parent proces             at ');
       writeln (WordHex (OuderSegment), ':0000');
    
       write ('Û = Parent environment        at ');
       if Ouder > $0000
          then writeln (WordHex (Ouder), ':0000')
          else writeln ('?');
    
       writeln ('> = Current proces            at ',
                WordHex (prefixseg), ':0000');
    
       writeln ('    returns                   to ',
                WordHex (TerugkeerSegment), ':', WordHex (TerugkeerOffset));
    END.
    

    (在ASCII 127之上,此演示文稿中可能存在一些ASCII / ANSI转换问题。)

答案 3 :(得分:1)

注册表的方式是,如果你想永久修改它,我想这就是你想要的,因为它在setup.py中。

暂时只是你的过程,然后os.environ是诀窍。

答案 4 :(得分:1)

在os模块中,有getenv和putenv函数。但是,似乎putenv无法正常工作,您必须使用Windows注册表

查看 this discussion

答案 5 :(得分:1)

如果没有权限回退到用户的注册表,这个Python脚本[*]会尝试修改注册表中的GLOBAL env-vars,然后通知所有窗口有关更改:

"""
Show/Modify/Append registry env-vars (ie `PATH`) and notify Windows-applications to pickup changes.

First attempts to show/modify HKEY_LOCAL_MACHINE (all users), and 
if not accessible due to admin-rights missing, fails-back 
to HKEY_CURRENT_USER.
Write and Delete operations do not proceed to user-tree if all-users succeed.

Syntax: 
    {prog}                  : Print all env-vars. 
    {prog}  VARNAME         : Print value for VARNAME. 
    {prog}  VARNAME   VALUE : Set VALUE for VARNAME. 
    {prog}  +VARNAME  VALUE : Append VALUE in VARNAME delimeted with ';' (i.e. used for `PATH`). 
    {prog}  -VARNAME        : Delete env-var value. 

Note that the current command-window will not be affected, 
changes would apply only for new command-windows.
"""

import winreg
import os, sys, win32gui, win32con

def reg_key(tree, path, varname):
    return '%s\%s:%s' % (tree, path, varname) 

def reg_entry(tree, path, varname, value):
    return '%s=%s' % (reg_key(tree, path, varname), value)

def query_value(key, varname):
    value, type_id = winreg.QueryValueEx(key, varname)
    return value

def show_all(tree, path, key):
    i = 0
    while True:
        try:
            n,v,t = winreg.EnumValue(key, i)
            print(reg_entry(tree, path, n, v))
            i += 1
        except OSError:
            break ## Expected, this is how iteration ends.

def notify_windows(action, tree, path, varname, value):
    win32gui.SendMessage(win32con.HWND_BROADCAST, win32con.WM_SETTINGCHANGE, 0, 'Environment')
    print("---%s %s" % (action, reg_entry(tree, path, varname, value)))

def manage_registry_env_vars(varname=None, value=None):
    reg_keys = [
        ('HKEY_LOCAL_MACHINE', r'SYSTEM\CurrentControlSet\Control\Session Manager\Environment'),
        ('HKEY_CURRENT_USER', r'Environment'),
    ]
    for (tree_name, path) in reg_keys:
        tree = eval('winreg.%s'%tree_name)
        try:
            with winreg.ConnectRegistry(None, tree) as reg:
                with winreg.OpenKey(reg, path, 0, winreg.KEY_ALL_ACCESS) as key:
                    if not varname:
                        show_all(tree_name, path, key)
                    else:
                        if not value:
                            if varname.startswith('-'):
                                varname = varname[1:]
                                value = query_value(key, varname)
                                winreg.DeleteValue(key, varname)
                                notify_windows("Deleted", tree_name, path, varname, value)
                                break  ## Don't propagate into user-tree.
                            else:
                                value = query_value(key, varname)
                                print(reg_entry(tree_name, path, varname, value))
                        else:
                            if varname.startswith('+'):
                                varname = varname[1:]
                                value = query_value(key, varname) + ';' + value
                            winreg.SetValueEx(key, varname, 0, winreg.REG_EXPAND_SZ, value)
                            notify_windows("Updated", tree_name, path, varname, value)
                            break  ## Don't propagate into user-tree.
        except PermissionError as ex:
            print("!!!Cannot access %s due to: %s" % 
                    (reg_key(tree_name, path, varname), ex))
        except FileNotFoundError as ex:
            print("!!!Cannot find %s due to: %s" % 
                    (reg_key(tree_name, path, varname), ex))

if __name__=='__main__':
    args = sys.argv
    argc = len(args)
    if argc > 3:
        print(__doc__.format(prog=args[0]))
        sys.exit()

    manage_registry_env_vars(*args[1:])

以下是一些使用示例,假设它已保存在当前路径中某个名为setenv.py的文件中。 请注意,在这些示例中,我没有管理员权限,因此更改仅影响我本地用户的注册表树:

> REM ## Print all env-vars
> setenv.py
!!!Cannot access HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session   Manager\Environment:PATH due to: [WinError 5] Access is denied
HKEY_CURRENT_USER\Environment:PATH=...
...

> REM ## Query env-var:
> setenv.py PATH C:\foo
!!!Cannot access HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session   Manager\Environment:PATH due to: [WinError 5] Access is denied
!!!Cannot find HKEY_CURRENT_USER\Environment:PATH due to: [WinError 2] The system cannot find the file specified

> REM ## Set env-var:
> setenv.py PATH C:\foo
!!!Cannot access HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session   Manager\Environment:PATH due to: [WinError 5] Access is denied
---Set HKEY_CURRENT_USER\Environment:PATH=C:\foo

> REM ## Append env-var:
> setenv.py +PATH D:\Bar
!!!Cannot access HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session   Manager\Environment:PATH due to: [WinError 5] Access is denied
---Set HKEY_CURRENT_USER\Environment:PATH=C:\foo;D:\Bar

> REM ## Delete env-var:
> setenv.py -PATH
!!!Cannot access HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session   Manager\Environment:PATH due to: [WinError 5] Access is denied
---Deleted HKEY_CURRENT_USER\Environment:PATH

[*]改编自:http://code.activestate.com/recipes/416087-persistent-environment-variables-on-windows/