Тонкости использования getenv() и putenv()

Нарвался тут на интересные грабли с функциями getenv() и putenv().

С putenv() у меня уже был интересный опыт.

Часто люди пишут так:

if (getenv("A_FLAG")) {
  ...
}

Это работает неплохо для переменных-флагов, которые либо есть, либо нет. Значение не важно.

Что получилось у меня:

int main(...) {
  putenv("GLOBAL_FLAG=1");  // Глобальное значение для всей программы.
  ...
  system("xyz");            // Это программа должна видеть GLOBAL_FLAG=1.
  ...
  do_stuff();
  ...
}

void do_stuff() {
  ...
  if (something) {
    putenv("GLOBAL_FLAG="); // Убрать переменную.
    system("abc");          // А вот для этой программы флаг должен быть убран.
    ...
  }
  ...
  if (getenv("GLOBAL_FLAG") {
     // И вот тут начиналась ерунда на разных платформах.
  }
}

А корень зла тут в том, что после putenv() результат getenv() может стать либо NULL, либо "", в зависимости от платформы.

Например:

if (getenv("GLOBAL_FLAG") {
    ...

работает только на Windows и правильнее писать:

const char* p = getenv("GLOBAL_FLAG");
if (p != NULL && *p != '\0') {
  ...
}

И лучше сделать wrapper для getenv():

std::string GetEnv(const char* name) {
  const char* v = getenv(name);
  return v ? v : "";
}

И для проверки писать:

if (!GetEnv("var").empty()) {
  ..
}

Для теста я написал программу, которая выставляет переменную и проверяет ее значение через getenv() и через вызов дочерней программы.

#include <string>
#include <vector>
#include <iostream>
#include <cstdlib>

#ifdef WINDOWS
#define putenv _putenv
#endif

void PrintVariableViaShell(const std::string& name) {
  std::cout << "Value from shell:" << std::endl;
  const std::string cmd =
#ifdef WINDOWS
    std::string("cmd /c echo [%") + name + "%]";
#else
    std::string("/bin/sh -c \"echo [$") + name + "]\"";
#endif
  std::cout << cmd << std::endl;
  std::system(cmd.c_str());
}

void PrintVariableViaGetEnv(const std::string& name) {
  std::cout << "Value from getenv():" << std::endl;
  const char* v = std::getenv(name.c_str());
  std::cout << "[" << (v ? v : "<NULL>") << "]" << std::endl;
}

void SetVariableDeleteAndPrint(const char* name_value, const bool equ) {
  const std::string& name_value_s(name_value);
  const std::string name = name_value_s.substr(0, name_value_s.find('='));

  putenv(const_cast<char*>(name_value));
  std::vector<char> delete_without_equ(name.begin(), name.end());
  delete_without_equ.push_back('\0');
  putenv(&delete_without_equ[0]);

  std::cout << "Value after deleting WITHOUT '=':" << std::endl;
  PrintVariableViaShell(name);
  PrintVariableViaGetEnv(name);

  std::cout << std::endl;

  putenv(const_cast<char*>(name_value));
  std::vector<char> delete_with_equ(name.begin(), name.end());
  delete_with_equ.push_back('=');
  delete_with_equ.push_back('\0');
  putenv(&delete_with_equ[0]);

  std::cout << "Value after deleting WITH '=': " << std::endl;
  PrintVariableViaShell(name);
  PrintVariableViaGetEnv(name);
}

int main(int argc, char* argv[]) {
#ifdef WINDOWS
  std::cout << "WINDOWS" << std::endl;
#else
  system("uname");
#endif
  SetVariableDeleteAndPrint("ABC=123", true);
  return 0;
}

И вот результы с разных платформ.

Linux

Linux
Value after deleting WITHOUT '=':
Value from shell:
/bin/sh -c "echo [$ABC]"
[]
Value from getenv():
[<NULL>]

Value after deleting WITH '=':
Value from shell:
/bin/sh -c "echo [$ABC]"
[]
Value from getenv():
[]

AIX

AIX
Value after deleting WITHOUT '=':
Value from shell:
/bin/sh -c "echo [$ABC]"
[123]
Value from getenv():
[123]

Value after deleting WITH '=':
Value from shell:
/bin/sh -c "echo [$ABC]"
[]
Value from getenv():
[]

SunOS

SunOS
Value after deleting WITHOUT '=':
Value from shell:
/bin/sh -c "echo [$ABC]"
[123]
Value from getenv():
[123]

Value after deleting WITH '=':
Value from shell:
/bin/sh -c "echo [$ABC]"
[]
Value from getenv():
[]

HP-UX

HP-UX
Value after deleting WITHOUT '=':
Value from shell:
/bin/sh -c "echo [$ABC]"
[123]
Value from getenv():
[123]

Value after deleting WITH '=':
Value from shell:
/bin/sh -c "echo [$ABC]"
[]
Value from getenv():
[]

WINDOWS

WINDOWS
Value after deleting WITHOUT '=':
Value from shell:
cmd /c echo [%ABC%]
[123]
Value from getenv():
[123]

Value after deleting WITH '=':
Value from shell:
cmd /c echo [%ABC%]
[%ABC%]
Value from getenv():
[<NULL>]

Только на Windows getenv() возвращает NULL после удаления. На остальных это будет пустая строка.

Забавно, на Linux можно удалять переменные через putenv("name") (без знака “=”), а тогда getenv() будет возвращать NULL.

Посты по теме:


Оригинальный пост | Disclaimer

Комментарии