Git Bash convierte rutas a C:/Program Files/Git: solución

Ejecutas un comando desde Git Bash que funciona sin problemas en otra terminal y el programa recibe algo distinto de lo que escribiste. Estos son los síntomas más habituales:

$ cmd /c script.bat
Microsoft Windows [Versión 10.0.xxxxx]
(c) Microsoft Corporation. Todos los derechos reservados.

C:\proyectos\mi-app>

$ ssh -i /home/usuario/.ssh/id_ed25519 mi-servidor
Warning: Identity file C:/Program Files/Git/home/usuario/.ssh/id_ed25519 not accessible: No such file or directory.

$ node leer.js G:\Mi unidad\datos\informe.txt
Error: ENOENT: no such file or directory, open 'G:Mi'

$ cd D:\proyectos\mi-app
bash: cd: D:proyectosmi-app: No such file or directory

También ocurre al pasar una ruta de Linux a docker exec o a wsl.exe: el programa recibe una ruta de Windows que no existe donde debería. Hay tres mecanismos distintos detrás, y cada uno tiene su arreglo.

Por qué pasa

1. Conversión automática de rutas. Git Bash se basa en MSYS2. La documentación de MSYS2 explica que, cuando se llama a un ejecutable nativo de Windows, los argumentos que parecen rutas Unix se convierten automáticamente a rutas de Windows, algo que según la propia documentación no es perfecto. Las notas de versión de Git for Windows describen el caso de las opciones que empiezan por una barra: /usr/bin/bash.exe se convierte en C:\Program Files\Git\usr\bin\bash.exe. La raíz / de MSYS corresponde a la carpeta de instalación de Git, y de ahí sale C:/Program Files/Git/.... Lo que recibe un programa nativo en una instalación de prueba:

/home/usuario/.ssh/id_ed25519   ->  C:/Program Files/Git/home/usuario/.ssh/id_ed25519
--dir=/foo                      ->  --dir=C:/Program Files/Git/foo
-L/regex/                       ->  -LC:/Program Files/Git/regex/
/c                              ->  C:/

La última línea explica el primer síntoma: cmd.exe ya no recibe /c, así que abre una sesión interactiva y no ejecuta el script. La conversión no ocurre en PowerShell ni en cmd: es una función de MSYS2 al lanzar programas nativos desde Bash.

2. La barra invertida es el carácter de escape de Bash. Fuera de comillas, Bash elimina la barra y conserva el carácter siguiente, de modo que D:\proyectos\mi-app llega como D:proyectosmi-app. Además, un espacio sin comillas divide el argumento en dos, como en G:\Mi unidad.

3. Las rutas /tmp, /home o /usr solo existen dentro de MSYS. Un programa nativo que recibe la cadena literal /tmp/archivo la interpreta como una ruta desde la raíz de la unidad actual. Por eso un archivo creado en /tmp desde Bash puede no encontrarse desde un Python o un Node nativos. Dependiendo de la versión y de la configuración de Git for Windows, /tmp puede apuntar a la carpeta temporal del usuario o a una subcarpeta de la instalación de Git; compruébalo con cygpath -w /tmp.

Solución

  1. Desactiva la conversión para un comando con MSYS_NO_PATHCONV=1, que es lo que indican las notas de versión de Git for Windows:

    MSYS_NO_PATHCONV=1 docker exec mi-contenedor pg_restore -U postgres -d mi_db /var/backups/respaldo.dump

    La ruta llega intacta al contenedor. Si prefieres desactivarla en toda la sesión, usa export MSYS_NO_PATHCONV=1, pero entonces tendrás que pasar tú mismo rutas válidas para Windows a los programas nativos.

  2. Excluye solo ciertos argumentos con MSYS2_ARG_CONV_EXCL. Según la documentación de MSYS2, acepta * para excluirlos todos o una lista de prefijos separados por ;:

    MSYS2_ARG_CONV_EXCL='/var;/home' docker exec mi-contenedor ls /var/backups

    Para las variables de entorno existe MSYS2_ENV_CONV_EXCL, que funciona de forma equivalente.

  3. Duplica la primera barra en las opciones. Las notas de versión de Git for Windows lo proponen como alternativa:

    cmd //c script.bat
    taskkill //F //PID 1234

    El programa recibe /c, /F y /PID sin convertir. Ten en cuenta que una ruta completa como //tmp/archivo tiene forma de ruta UNC y puede llegar al programa con las dos barras, sin convertir; para rutas completas es más fiable MSYS_NO_PATHCONV=1.

  4. Escribe las rutas de Windows con barras normales o entre comillas simples. Windows acepta / como separador, y las comillas simples impiden que Bash interprete la barra invertida:

    cd D:/proyectos/mi-app
    node leer.js 'G:\Mi unidad\datos\informe.txt'
  5. Convierte tú mismo con cygpath. La documentación de Cygwin indica que -w imprime la forma de Windows con barras invertidas y -m la misma ruta con barras normales, útil en scripts de shell:

    node leer.js "$(cygpath -m /tmp/datos.json)"
    ssh -i ~/.ssh/id_ed25519 mi-servidor

    En el caso de SSH, usa ~ en lugar de /home/usuario: la tilde se expande a la carpeta de usuario de Windows y el cliente nativo encuentra la clave.

  6. Para WSL, ejecuta el comando de Linux dentro de wsl.exe. La documentación de Microsoft indica que los comandos pasados a wsl.exe se reenvían sin modificar y que las rutas deben escribirse en formato WSL. Entre comillas simples, Git Bash no ve la ruta como argumento propio y no la toca:

    wsl.exe -e bash -lc 'ls /tmp && echo $HOME'

    Las comillas simples evitan que Git Bash expanda $HOME antes de enviar el comando.

Cómo verificar que quedó resuelto

Cualquier programa nativo que imprima sus argumentos sirve de prueba. Con Python:

python -c "import sys; print(sys.argv[1:])" /var/backups/respaldo.dump
MSYS_NO_PATHCONV=1 python -c "import sys; print(sys.argv[1:])" /var/backups/respaldo.dump

El primer comando muestra una ruta bajo C:/Program Files/Git/...; el segundo debe imprimir ['/var/backups/respaldo.dump']. Para cmd, comprueba que cmd //c echo hola imprime hola en lugar de abrir una sesión interactiva. Después repite el comando original (por ejemplo, la restauración con pg_restore dentro del contenedor) y confirma que termina sin errores de ruta.

Variantes y errores relacionados

Montar una carpeta con docker run -v

Un argumento como "$PWD":/datos contiene dos rutas separadas por dos puntos, y MSYS lo convierte en una lista separada por punto y coma (C:\...;C:\Program Files\Git\datos), que Docker no entiende. Pasa una ruta de Windows con barras normales en la parte del equipo anfitrión, que cygpath -m genera, y comprueba qué formato admite tu versión de Docker:

MSYS_NO_PATHCONV=1 docker run --rm -v "$(cygpath -m "$PWD")":/datos mi-imagen ls /datos

Se antepone MSYS_NO_PATHCONV=1 porque el argumento final, /datos, también empieza por una barra y docker.exe lo recibiría convertido.

docker cp no se ve afectado

En docker cp ./respaldo.dump mi-contenedor:/var/backups/ ningún argumento empieza por una barra, así que no se convierte nada. Si el destino empieza por /, aplican los pasos anteriores.

Archivos que Bash crea en /tmp y un programa nativo no encuentra

Pasa a la herramienta la ruta de Windows que devuelve cygpath -m, o crea los archivos de intercambio en una carpeta con ruta de Windows conocida.

Conviene saber qué se convierte

La conversión solo afecta a los argumentos (y variables de entorno) de programas nativos. Los comandos internos de Bash y los programas de MSYS, como ls, reciben las rutas tal cual.

Resumen

  • MSYS2 convierte los argumentos que parecen rutas Unix al llamar a programas nativos de Windows; por eso aparecen rutas como C:/Program Files/Git/... y cmd /c abre una sesión interactiva.
  • Bash trata la barra invertida como carácter de escape: usa barras normales o comillas simples en las rutas de Windows.
  • Desactiva la conversión con MSYS_NO_PATHCONV=1 en el comando, o limítala con MSYS2_ARG_CONV_EXCL.
  • Para las opciones, duplica la barra: cmd //c. Para pasar rutas reales de Windows, usa cygpath -m.
  • Los comandos de Linux van dentro de wsl.exe -e bash -lc '...'.

Referencias

Comentarios