Nushell
문서
쿡북
블로그
  • English
  • 中文
  • Deutsch
  • Français
  • Español
  • 日本語
  • Português do Brasil
  • Русский язык
  • 한국어
GitHub
문서
쿡북
블로그
  • English
  • 中文
  • Deutsch
  • Français
  • Español
  • 日本語
  • Português do Brasil
  • Русский язык
  • 한국어
GitHub
  • 소개
  • 설치하기
    • 기본 셸
  • 시작하기
    • 빠른 둘러보기
    • 시스템 이동
    • 누셸 방식으로 생각하기
    • 누셸 치트 시트
  • Nu 기본
    • 데이터 유형
    • 데이터 로드
    • 파이프라인
    • 문자열 작업
    • 목록 작업
    • 레코드 작업
    • 테이블 작업
    • 구조화된 데이터 탐색 및 액세스
    • 특수 변수
  • Nu에서 프로그래밍하기
    • 사용자 지정 명령
    • 별칭
    • 연산자
    • 변수
    • 제어 흐름
    • 스크립트
    • 모듈
      • 모듈 사용하기
      • 모듈 만들기
    • 오버레이
    • 정렬
    • 누셸 코드 테스트
    • 모범 사례
  • 셸로서의 Nu
    • 구성
    • 환경
    • 표준 출력, 표준 오류 및 종료 코드
    • 시스템(외부) 명령 실행
    • 타사 프롬프트 구성 방법
    • 디렉터리 스택
    • Reedline, Nu의 줄 편집기
    • 사용자 지정 완성
    • 외부 명령
    • Nu의 색상 지정 및 테마 설정
    • 후크
    • 백그라운드 작업
  • Nu로 전환하기
    • Bash에서 오신 분들을 위해
    • CMD.EXE에서 오신 분들을 위해
    • 다른 셸 및 도메인 특정 언어의 Nu 맵
    • 명령형 언어의 Nu 맵
    • 함수형 언어의 Nu 맵
    • 누셸 연산자 맵
  • 디자인 노트
    • 누셸 코드가 실행되는 방법
  • (약간)고급 사용법
    • 표준 라이브러리 (미리보기)
    • 데이터프레임
    • 메타데이터
    • 나만의 오류 만들기
    • 병렬 처리
    • 플러그인
    • explore

누셸 코드가 실행되는 방법

누셸 방식으로 생각하기에서 누셸 코드가 처리되는 방식 때문에 *"누셸을 컴파일된 언어로 생각하라"*고 권장했습니다. 또한 해당 프로세스로 인해 누셸에서 작동하지 않는 여러 코드 예제를 다루었습니다.

이에 대한 근본적인 이유는 **eval과 유사한 기능을 허용하지 않는 파싱 및 평가 단계의 엄격한 분리**입니다. 이 섹션에서는 이것이 무엇을 의미하는지, 왜 그렇게 하는지, 그리고 그 의미가 무엇인지 자세히 설명합니다. 설명은 가능한 한 간단하게 하려고 하지만, 이전에 다른 언어로 프로그래밍한 경험이 있다면 도움이 될 수 있습니다.

  • 인터프리터 언어 대 컴파일 언어
    • 인터프리터 언어
    • 컴파일 언어
  • 동적 언어 대 정적 언어
    • Eval 함수
  • 영향
  • 누셸 REPL
    • 여러 줄 REPL 명령줄
  • 구문 분석 시간 상수 평가

인터프리터 언어 대 컴파일 언어

인터프리터 언어

누셸, 파이썬, Bash(그리고 다른 많은 언어)는 "인터프리터" 언어입니다.

간단한 "Hello, World!" 누셸 프로그램으로 시작하겠습니다.

# hello.nu

print "Hello, World!"

물론 이것은 nu hello.nu를 사용하여 예상대로 실행됩니다. 파이썬이나 Bash로 작성된 유사한 프로그램은 거의 동일하게 보이고 동작합니다.

*"인터프리터 언어"*에서 코드는 일반적으로 다음과 같이 처리됩니다.

소스 코드 → 인터프리터 → 결과

누셸은 이 패턴을 따르며, "인터프리터"는 두 부분으로 나뉩니다.

  1. 소스 코드 → 파서 → 중간 표현(IR)
  2. IR → 평가 엔진 → 결과

먼저 소스 코드가 파서에 의해 분석되고 중간 표현(IR)으로 변환되는데, 누셸의 경우 이는 단지 데이터 구조의 모음입니다. 그런 다음 이러한 데이터 구조는 평가 및 결과 출력을 위해 엔진에 전달됩니다.

이것 역시 인터프리터 언어에서 일반적입니다. 예를 들어 파이썬의 소스 코드는 일반적으로 평가 전에 바이트코드로 변환됩니다.

컴파일 언어

반면에 C, C++, Rust와 같이 일반적으로 "컴파일"되는 언어가 있습니다. 예를 들어, Rust로 작성된 간단한 *"Hello, World!"*입니다.

// main.rs

fn main() {
    println!("Hello, World!");
}

이 코드를 "실행"하려면 다음을 수행해야 합니다.

  1. 기계어 명령어로 컴파일
  2. 컴파일 결과를 디스크에 바이너리 파일로 저장

처음 두 단계는 rustc main.rs로 처리됩니다.

  1. 그런 다음 결과를 생성하려면 바이너리(/.main)를 실행해야 하며, 이는 명령어를 CPU에 전달합니다.

그래서:

  1. 소스 코드 ⇒ 컴파일러 ⇒ 기계어
  2. 기계어 ⇒ CPU ⇒ 결과

중요

컴파일-실행 시퀀스는 인터프리터의 파싱-평가 시퀀스와 크게 다르지 않다는 것을 알 수 있습니다. 소스 코드로 시작하여 일부 상태(예: 바이트코드, IR, 기계어)로 파싱(또는 컴파일)한 다음 IR을 평가(또는 실행)하여 결과를 얻습니다. 기계어는 단지 다른 유형의 IR이고 CPU는 해당 인터프리터라고 생각할 수 있습니다.

그러나 인터프리터 언어와 컴파일 언어의 한 가지 큰 차이점은 인터프리터 언어는 일반적으로 eval 함수를 구현하는 반면 컴파일 언어는 그렇지 않다는 것입니다. 이것은 무엇을 의미합니까?

동적 언어 대 정적 언어

용어

일반적으로 동적 언어와 정적 언어의 차이점은 컴파일(또는 파싱) 대 평가/런타임 중에 소스 코드의 어느 정도가 해결되는지에 있습니다.

  • "정적" 언어는 컴파일/파싱 중에 더 많은 코드 분석(예: 형식 검사, 데이터 소유권)을 수행합니다.

  • "동적" 언어는 평가/런타임 중에 추가 코드의 eval을 포함하여 더 많은 코드 분석을 수행합니다.

이 논의의 목적상 정적 언어와 동적 언어의 주요 차이점은 eval 함수가 있는지 여부입니다.

Eval 함수

대부분의 동적 인터프리터 언어에는 eval 함수가 있습니다. 예를 들어, Python eval(또한 Python exec) 또는 Bash eval이 있습니다.

eval의 인수는 *"소스 코드 내부의 소스 코드"*이며, 일반적으로 조건부 또는 동적으로 계산됩니다. 즉, 인터프리터 언어가 파싱/평가 중에 소스 코드에서 eval을 만나면 일반적으로 정상적인 평가 프로세스를 중단하고 eval의 소스 코드 인수에 대한 새로운 파싱/평가를 시작합니다.

다음은 이 (잠재적으로 혼란스러운!) 개념을 설명하기 위한 간단한 Python eval 예제입니다.

# hello_eval.py

print("Hello, World!")
eval("print('Hello, Eval!')")

파일(python hello_eval.py)을 실행하면 *"Hello, World!"*와 *"Hello, Eval!"*이라는 두 가지 메시지가 표시됩니다. 다음은 발생하는 일입니다.

  1. 전체 프로그램이 파싱됩니다.
  2. (3행) print("Hello, World!")가 평가됩니다.
  3. (4행) eval("print('Hello, Eval!')")을 평가하기 위해:
    1. print('Hello, Eval!')가 파싱됩니다.
    2. print('Hello, Eval!')가 평가됩니다.

더 재미있는 것

eval("eval(\"print('Hello, Eval!')\")") 등을 고려하십시오!

여기서 eval을 사용하면 실행 프로세스에 새로운 "메타" 단계가 추가됩니다. 단일 파싱/평가 대신 eval은 추가적인 "재귀적" 파싱/평가 단계를 만듭니다. 즉, 파이썬 인터프리터가 생성한 바이트코드는 평가 중에 추가로 수정될 수 있습니다.

누셸은 이를 허용하지 않습니다.

위에서 언급했듯이 해석 과정 중에 바이트코드를 수정할 eval 함수가 없으면 인터프리터 언어의 파싱/평가 과정과 C++ 및 Rust와 같은 컴파일 언어의 컴파일/실행 과정 사이에는 (높은 수준에서) 거의 차이가 없습니다.

요점

이것이 우리가 *"누셸을 컴파일된 언어로 생각하라"*고 권장하는 이유입니다. 인터프리터 언어임에도 불구하고 eval이 없기 때문에 기존의 정적, 컴파일 언어에서 흔히 볼 수 있는 특징적인 이점과 제한 사항을 일부 공유합니다.

다음 섹션에서는 이것이 무엇을 의미하는지 더 자세히 살펴보겠습니다.

영향

이 파이썬 예제를 고려하십시오.

exec("def hello(): print('Hello eval!')")
hello()

노트

이 예제에서는 eval이 eval 표현식으로 제한되는 대신 유효한 파이썬 코드를 실행할 수 있기 때문에 eval 대신 exec를 사용합니다. 그러나 원칙은 두 경우 모두 유사합니다.

해석 중:

  1. 전체 프로그램이 파싱됩니다.
  2. 1행을 평가하기 위해:
    1. def hello(): print('Hello eval!')가 파싱됩니다.
    2. def hello(): print('Hello eval!')가 평가됩니다.
  3. (2행) hello()가 평가됩니다.

2.2 단계까지 인터프리터는 hello라는 함수가 존재하는지조차 모릅니다! 이로 인해 동적 언어의 정적 분석이 어려워집니다. 이 예제에서 hello 함수의 존재는 소스 코드를 파싱(컴파일)하는 것만으로는 확인할 수 없습니다. 인터프리터는 코드를 평가(실행)하여 이를 발견해야 합니다.

  • 정적, 컴파일 언어에서는 누락된 함수가 컴파일 시간에 잡히는 것이 보장됩니다.
  • 그러나 동적, 인터프리터 언어에서는 가능한 런타임 오류가 됩니다. eval로 정의된 함수가 조건부로 호출되는 경우 해당 조건이 프로덕션에서 충족될 때까지 오류가 발견되지 않을 수 있습니다.

중요

누셸에는 정확히 두 단계가 있습니다.

  1. 전체 소스 코드 파싱
  2. 전체 소스 코드 평가

이것이 완전한 파싱/평가 시퀀스입니다.

요점

eval과 유사한 기능을 허용하지 않음으로써 누셸은 이러한 유형의 eval 관련 버그를 방지합니다. 존재하지 않는 정의를 호출하는 것은 누셸에서 구문 분석 시에 잡히는 것이 보장됩니다.

또한 구문 분석이 완료된 후에는 바이트코드(IR)가 평가 중에 변경되지 않을 것이라고 확신할 수 있습니다. 이를 통해 결과 바이트코드(IR)에 대한 깊은 통찰력을 얻을 수 있으므로 보다 동적인 언어로 달성하기 어려운 강력하고 신뢰할 수 있는 정적 분석 및 IDE 통합이 가능합니다.

일반적으로 누셸 프로그램을 확장할 때 오류가 더 일찍 잡힐 것이라는 확신을 가질 수 있습니다.

누셸 REPL

대부분의 셸과 마찬가지로 누셸에는 파일 없이 nu를 실행할 때 시작되는 "읽기→평가→인쇄 루프"(REPL)가 있습니다. 이것은 종종 *"명령줄"*과 같다고 생각되지만 실제로는 그렇지 않습니다.

참고

이 섹션에서 코드 블록의 줄 시작 부분에 있는 > 문자는 명령줄 **프롬프트**를 나타내는 데 사용됩니다. 예시:

> some code...

다음 예제의 프롬프트 뒤에 있는 코드는 Enter 키를 눌러 실행됩니다. 예시:

> print "Hello world!"
# => Hello world!

> ls
# => prints files and directories...

위의 내용은 다음과 같습니다.

  • 누셸 내부에서(nu로 시작):
    1. print "Hello world!" 입력
    2. Enter 누르기
    3. 누셸이 결과를 표시합니다.
    4. ls 입력
    5. Enter 누르기
    6. 누셸이 결과를 표시합니다.

명령줄을 입력한 후 Enter를 누르면 누셸은 다음을 수행합니다.

  1. (읽기): 명령줄 입력을 읽습니다.
  2. (평가): 명령줄 입력을 구문 분석합니다.
  3. (평가): 명령줄 입력을 평가합니다.
  4. (평가): 환경(예: 현재 작업 디렉터리)을 내부 누셸 상태에 병합합니다.
  5. (인쇄): 결과(비-null인 경우)를 표시합니다.
  6. (루프): 다른 입력을 기다립니다.

즉, 각 REPL 호출은 자체적인 별도의 구문 분석-평가 시퀀스입니다. 환경을 누셸의 상태에 다시 병합함으로써 REPL 호출 간의 연속성을 유지합니다.

*"누셸 방식으로 생각하기"*에서 cd 예제의 단순화된 버전을 비교해 보십시오.

cd spam
source-env foo.nu

거기서 우리는 디렉터리가 구문 분석 시간 source-env 키워드가 파일을 읽으려고 시도한 후에 변경되기 때문에 이것이 (스크립트나 다른 단일 표현식으로) 작동할 수 없다는 것을 보았습니다.

그러나 이러한 명령을 별도의 REPL 항목으로 실행하면 작동합니다.

> cd spam
> source-env foo.nu
# 야호, 작동합니다!

이유를 보려면 예제에서 무슨 일이 일어나는지 분석해 보겠습니다.

  1. cd spam 명령줄을 읽습니다.
  2. cd spam 명령줄을 구문 분석합니다.
  3. cd spam 명령줄을 평가합니다.
  4. 환경(현재 디렉터리 포함)을 누셸 상태에 병합합니다.
  5. source-env foo.nu를 읽고 구문 분석합니다.
  6. source-env foo.nu를 평가합니다.
  7. 환경(foo.nu의 모든 변경 사항 포함)을 누셸 상태에 병합합니다.

5단계에서 구문 분석 중에 source-env가 foo.nu를 열려고 할 때 3단계의 디렉터리 변경 사항이 4단계에서 누셸 상태에 병합되었기 때문에 그렇게 할 수 있습니다. 결과적으로 다음 구문 분석/평가 주기에서 볼 수 있습니다.

여러 줄 REPL 명령줄

이것은 별도의 명령줄에만 작동한다는 점을 명심하십시오.

누셸에서는 다음을 사용하여 여러 명령을 하나의 명령줄로 그룹화할 수 있습니다.

  • 세미콜론:

    cd spam; source-env foo.nu
  • 줄 바꿈:

    > cd span
      source-env foo.nu

    두 번째 줄 앞에 "프롬프트"가 없다는 점에 유의하십시오. 이러한 유형의 여러 줄 명령줄은 일반적으로 Alt+Enter 또는 Shift+ Enter를 누를 때 줄 바꿈을 삽입하기 위해 키 바인딩으로 만들어집니다.

이 두 예제는 누셸 REPL에서 정확히 동일하게 작동합니다. 전체 명령줄(두 문 모두)은 단일 읽기→평가→인쇄 루프로 처리됩니다. 따라서 이전 스크립트 예제와 동일한 방식으로 실패합니다.

팁

여러 줄 명령줄은 누셸에서 매우 유용하지만 순서가 잘못된 파서 키워드에 주의하십시오.

구문 분석 시간 상수 평가

평가 단계에 구문 분석을 추가하면서도 정적 언어의 이점을 유지하는 것은 불가능하지만, 구문 분석에 약간의 평가를 안전하게 추가할 수 있습니다.

용어

아래 텍스트에서 *"상수"*라는 용어는 다음을 나타내는 데 사용됩니다.

  • const 정의
  • 상수 입력을 제공할 때 상수 값을 출력하는 모든 명령의 결과. :::

본질적으로 **상수**와 상수 값은 구문 분석 시간에 알려져 있습니다. 물론 이것은 변수 선언 및 값과 극명한 대조를 이룹니다.

결과적으로 상수를 source, use 및 관련 명령과 같은 구문 분석 시간 키워드에 대한 안전하고 알려진 인수로 활용할 수 있습니다.

*"누셸 방식으로 생각하기"*에서 이 예제를 고려하십시오.

let my_path = "~/nushell-files"
source $"($my_path)/common.nu"

거기서 언급했듯이, 우리는 대신 다음을 수행할 수 있습니다.

const my_path = "~/nushell-files"
source $"($my_path)/common.nu"

이 버전에 대한 구문 분석/평가 프로세스를 분석해 보겠습니다.

  1. 전체 프로그램이 IR로 구문 분석됩니다.

    1. 1행: const 정의가 구문 분석됩니다. 상수 할당(그리고 const도 파서 키워드)이므로 이 단계에서 해당 할당도 평가될 수 있습니다. 이름과 값은 파서에 의해 저장됩니다.
    2. 2행: source 명령이 구문 분석됩니다. source도 파서 키워드이므로 이 단계에서 평가됩니다. 그러나 이 예제에서는 인수가 알려져 있고 이 시점에서 검색할 수 있으므로 성공적으로 구문 분석될 수 있습니다.
    3. ~/nushell-files/common.nu의 소스 코드가 구문 분석됩니다. 유효하지 않으면 오류가 생성되고, 그렇지 않으면 IR 결과가 다음 단계의 평가에 포함됩니다.
  2. 전체 IR이 평가됩니다.

    1. 1행: const 정의가 평가됩니다. 변수가 런타임 스택에 추가됩니다.
    2. 2행: ~/nushell-files/common.nu 구문 분석의 IR 결과가 평가됩니다.

중요

  • eval은 평가 중에 추가 구문 분석을 추가합니다.
  • 구문 분석 시간 상수는 반대로 작동하여 파서에 추가 평가를 추가합니다. :::

또한 구문 분석 중에 허용되는 평가는 **매우 제한적**이라는 점을 명심하십시오. 일반 평가 중에 허용되는 것의 작은 하위 집합으로만 제한됩니다.

예를 들어 다음은 허용되지 않습니다.

const foo_contents = (open foo.nu)

다르게 말하면, 작은 하위 집합의 명령과 표현식만 상수 값을 생성할 수 있습니다. 명령이 허용되려면:

  • 상수 값을 출력하도록 설계되어야 합니다.
  • 모든 입력도 상수 값, 리터럴 또는 리터럴의 복합 유형(예: 레코드, 목록, 테이블)이어야 합니다.

일반적으로 명령과 결과 표현식은 매우 간단하고 부작용이 없습니다. 그렇지 않으면 파서가 너무 쉽게 복구할 수 없는 상태에 빠질 수 있습니다. 예를 들어 상수에 무한 스트림을 할당하려고 시도한다고 상상해 보십시오. 구문 분석 단계가 절대 완료되지 않을 것입니다!

팁

다음과 같이 어떤 누셸 명령이 상수 값을 반환할 수 있는지 볼 수 있습니다.

help commands | where is_const

예를 들어, path join 명령은 상수 값을 출력할 수 있습니다. 누셸은 또한 $nu 상수 레코드에 몇 가지 유용한 경로를 정의합니다. 이를 결합하여 다음과 같은 유용한 구문 분석 시간 상수 평가를 만들 수 있습니다.

const my_startup_modules =  $nu.default-config-dir | path join "my-mods"
use $"($my_startup_modules)/my-utils.nu"

추가 참고 사항

컴파일된("정적") 언어는 컴파일 시간에 일부 논리를 전달하는 방법도 가지고 있는 경향이 있습니다. 예시:

  • C의 전처리기
  • Rust 매크로
  • Zig의 comptime, 누셸의 구문 분석 시간 상수 평가에 영감을 주었습니다.

이에 대한 두 가지 이유가 있습니다.

  1. 런타임 성능 향상: 컴파일 단계의 논리는 런타임 중에 반복할 필요가 없습니다.

    구문 분석된 결과(IR)가 평가를 넘어 저장되지 않기 때문에 현재 누셸에는 적용되지 않습니다. 그러나 이것은 확실히 가능한 미래 기능으로 고려되었습니다.

  2. 누셸의 구문 분석 시간 상수 평가와 마찬가지로 이러한 기능은 eval 함수가 없을 때 발생하는 제한 사항을 (안전하게) 해결하는 데 도움이 됩니다. :::

결론

누셸은 일반적으로 파이썬, Bash, Zsh, Fish 및 기타 많은 언어와 같이 "동적", "인터프리터" 언어가 지배하는 스크립팅 언어 공간에서 작동합니다. 누셸은 코드가 즉시 실행되기 때문에(별도의 수동 컴파일 없이) *"인터프리터"*이기도 합니다.

그러나 eval 구문이 없기 때문에 *"동적"*은 아닙니다. 이 점에서 Rust나 Zig와 같은 "정적", 컴파일된 언어와 더 많은 공통점을 공유합니다.

eval이 없다는 것은 많은 새로운 사용자에게 종종 놀라움을 주며, 이것이 누셸을 컴파일된 정적 언어로 생각하는 것이 도움이 될 수 있는 이유입니다.

GitHub에서 수정하기
Contributors: Taeyoon Kim, ImgBotApp, google-labs-jules[bot], deepthought
Prev
디자인 노트
Next
(그렇게) 고급은 아님