본문 바로가기
빌드노트 검색

빌드노트

PowerShell에서 npm run을 했는데 스크립트는 cmd로 해석된다

PowerShell에서 npm을 호출해도 Windows의 기본 스크립트 셸은 cmd.exe다. 같은 환경값의 직접 실행·npm 실행·셸 지정 결과를 비교하고, script-shell 조회와 명령 단위 지정, Node process.env로 옮기는 선택을 설명한다.

PowerShell에서 npm run을 실행했는데 package.json의 echo $env:LAB_NPM_SHELL_VALUE가 값 대신 변수 표기를 그대로 출력할 수 있다. Windows에서 npm 스크립트의 기본 셸은 cmd.exe이기 때문이다. npm을 호출한 터미널이 PowerShell이어도, 스크립트를 해석할 셸은 npm의 script-shell 설정에 따라 따로 정해진다. npm 10.9.8의 공식 실행 규칙 (새 탭에서 열림)

따라서 먼저 현재 설정을 확인한다. PowerShell 구문을 유지할 작업이면 해당 호출에 셸을 명시할 수 있다. 여러 환경에서 사용할 단순한 변수 읽기라면 작은 Node 파일로 옮기는 방법도 있다.

현재 패키지의 script-shell부터 확인한다

문제가 난 package.json이 있는 폴더에서 PowerShell로 다음 두 값을 읽는다. 첫 명령은 npm의 스크립트 셸 설정을, 둘째는 Windows 명령 셸의 경로를 확인한다.

npm.cmd config get script-shell
$env:ComSpec

별도 셸 설정이 없는 격리 패키지에서 직접 확인한 출력은 다음과 같았다.

null
C:\WINDOWS\system32\cmd.exe

여기서 null은 스크립트를 실행할 셸이 없다는 뜻이 아니다. 명시한 셸 설정이 없는 이 환경에서는 Windows 기본값인 cmd.exe가 사용됐다. pwsh.exe 등의 경로가 나온다면 이미 사용자 지정 셸을 쓰고 있으므로, 그 셸의 문법으로 스크립트를 읽어야 한다. .npmrc와 npm_config_script_shell 환경변수도 설정의 출처가 될 수 있다. npm 설정의 우선순위와 script-shell (새 탭에서 열림)

이 기본값은 npm이 스크립트 실행에 사용하는 값이다. Windows Terminal에서 어떤 프로필을 열었는지로 결정하지 않는다. 아래 명령은 Windows의 npm.cmd 호출 파일을 사용한다.

같은 환경값인데 표기법에 따라 출력이 달랐다

직접 비교한 환경은 Windows 빌드 10.0.26200.0, PowerShell 7.6.6, Node.js 22.23.2, npm 10.9.8이다. 비교하려면 Node와 npm이 설치된 상태에서 빈 폴더에 다음 package.json을 저장한다. 외부 의존성이 없어서 설치 명령은 필요하지 않다. 두 echo 스크립트는 같은 환경변수를 서로 다른 셸 문법으로 읽는다.

{
  "name": "npm-shell-lab",
  "private": true,
  "scripts": {
    "ps-env": "echo $env:LAB_NPM_SHELL_VALUE",
    "cmd-env": "echo %LAB_NPM_SHELL_VALUE%",
    "node-env": "node show-env.cjs"
  }
}

이 폴더의 PowerShell에서 합성 환경변수 하나를 만들고, 직접 실행한 결과와 npm을 거친 결과를 비교한다. --silent는 npm의 실행 안내를 줄여 스크립트 출력을 보기 위해 붙였다.

$env:LAB_NPM_SHELL_VALUE = 'from-parent'
echo $env:LAB_NPM_SHELL_VALUE
npm.cmd run --silent ps-env
npm.cmd run --silent cmd-env

값을 출력하는 세 명령의 결과는 순서대로 다음과 같았다. 두 npm 명령은 모두 종료 코드가 0이었다.

from-parent
$env:LAB_NPM_SHELL_VALUE
from-parent

직접 입력한 echo에서는 PowerShell이 $env:LAB_NPM_SHELL_VALUE를 값으로 바꿨다. ps-env의 같은 문자열은 cmd.exe로 넘어가 변수 표기 그대로 출력됐다. cmd-env에서는 %LAB_NPM_SHELL_VALUE%가 값으로 바뀌었다. 종료 코드가 0이어도 기대한 변수 확장이 일어났다고 볼 수는 없었다.

이 비교에서는 환경값 자체가 npm의 자식 프로세스에 도착했다. cmd-env가 같은 값을 읽었다는 것이 그 근거다. 부모 PowerShell이 package.json의 문자열까지 미리 해석해 주는 것은 아니므로, 변수 표기가 그대로 나왔다면 우선 그 문자열을 받은 셸의 문법을 확인한다.

PowerShell 구문이 필요하면 이번 호출에 셸을 지정한다

앞에서 만든 환경변수와 패키지를 그대로 둔다. PowerShell 7이 설치돼 있어야 하며, Get-Command로 찾은 실행 파일 경로를 npm 옵션에 전달한다.

$scriptShell = (Get-Command pwsh.exe -ErrorAction Stop).Source
npm.cmd --script-shell="$scriptShell" run --silent ps-env
npm.cmd run --silent ps-env
npm.cmd config get script-shell

이번 출력은 차례로 다음과 같았다.

from-parent
$env:LAB_NPM_SHELL_VALUE
null

첫 호출에서는 PowerShell이 스크립트를 해석해 값을 읽었다. 옵션을 뺀 다음 호출은 다시 기본 셸의 결과로 돌아갔고, 설정 조회도 처음처럼 null이었다. 명령에 준 --script-shell은 그 npm 호출에 적용되며 설정 파일에 저장되지 않는다. 이 옵션은 run 뒤에 별도 --를 붙여 전달할 스크립트 인자가 아니라 npm이 읽을 설정이다. npm의 명령행 설정 (새 탭에서 열림)

실제 실행 파일은 C:\Program Files\PowerShell\7\pwsh.exe였고, 위 인용 방식으로 공백을 포함한 경로도 전달됐다. Get-Command가 실패한다면 먼저 PowerShell 7의 설치와 실행 파일 위치를 확인한다. script-shell 값에는 실행 파일 경로를 넣는다.

셸을 바꾸면 다른 스크립트가 기대하는 문법도 달라진다. 같은 PowerShell 지정으로 cmd-env를 실행했을 때는 %LAB_NPM_SHELL_VALUE%가 그대로 출력됐다. 따라서 이 한 스크립트를 고치려고 전역 설정을 바꾸기 전에, 명령 단위로 지정해 필요한 동작을 확인하는 편이 낫다.

변수 읽기만 필요하면 Node 파일로 옮긴다

이 예제는 PowerShell 기능 자체보다 환경값 하나를 읽는 것이 목적이다. 같은 폴더의 show-env.cjs를 다음 한 줄로 작성하면, 앞서 등록한 node-env가 셸의 변수 확장 구문 없이 값을 읽을 수 있다.

console.log(process.env.LAB_NPM_SHELL_VALUE ?? '(missing)');

앞에서 만든 환경변수가 남아 있는 PowerShell에서 기본 셸과 지정한 PowerShell을 각각 거쳐 실행한다.

npm.cmd run --silent node-env
npm.cmd --script-shell="$scriptShell" run --silent node-env

두 명령 모두 from-parent를 출력했고 종료 코드는 0이었다. npm은 여전히 셸을 거쳐 node를 시작하지만, 환경변수를 읽는 코드는 Node 안에서 실행된다. PowerShell 문서는 환경변수가 자식 프로세스에 상속된다고 설명하며, Node 문서는 process.env를 현재 프로세스의 환경을 읽는 객체로 설명한다. PowerShell 환경변수 (새 탭에서 열림), Node.js 22.23.2의 process.env (새 탭에서 열림)

(missing)이 나오면 이 코드는 실행됐지만 해당 환경변수는 전달되지 않은 것이다. 같은 부모 세션에서 $env:LAB_NPM_SHELL_VALUE에 값을 넣었는지 확인한다. PowerShell 구문이 그대로 출력되는 앞선 경우와 구별할 수 있다.

PowerShell 기능이 필요한 작업은 실행 셸을 명시하고 그 설치를 실행 조건에 포함한다. 여러 OS에서 쓸 단순한 환경값 처리라면 package.json에는 node show-env.cjs처럼 실행 명령만 두고, 변수 읽기와 처리를 Node 파일에 모을 수 있다. 이번에 실제 실행한 것은 Windows에서의 두 셸 비교다. Linux·macOS, npm 11, Windows PowerShell 5.1에서는 실행하지 않았으므로 그 환경의 동작까지 실측한 결과로 보지는 않는다.

← 전체 글 보기