누셸 방식으로 생각하기
누셸은 다릅니다! 새로운 사용자가 다른 셸이나 언어에서 온 기존의 "습관"이나 정신 모델을 가지고 있는 것은 일반적이며 예상되는 일입니다!
새로운 사용자의 가장 일반적인 질문은 일반적으로 다음 주제 중 하나에 해당합니다.
누셸은 Bash가 아닙니다
때로는 Bash처럼 보일 수 있습니다
누셸은 프로그래밍 언어이자 셸입니다. 이 때문에 파일, 디렉터리, 웹사이트 등을 다루는 고유한 방식이 있습니다. 누셸의 일부 기능은 다른 셸에서 익숙한 기능과 유사하게 작동한다는 것을 알게 될 것입니다. 예를 들어, 파이프라인은 다른 셸과 마찬가지로 두 개 이상의 명령을 함께 결합하여 작동합니다.
예를 들어, 다음 명령줄은 유닉스/리눅스 플랫폼의 Bash와 누셸 모두에서 동일하게 작동합니다.
curl -s https://api.github.com/repos/nushell/nushell/contributors | jq -c '.[] | {login,contributions}'
# => 누셸에 대한 기여자를 기여 횟수 순으로 반환합니다.
누셸은 Bash(및 다른 셸)와 많은 유사점을 가지고 있으며 공통된 명령도 많습니다.
팁
Bash는 주로 외부 명령을 실행하는 명령 인터프리터입니다. 누셸은 이러한 많은 명령을 크로스 플랫폼, 내장 명령으로 제공합니다.
위 명령줄은 두 셸 모두에서 작동하지만 누셸에서는 curl
및 jq
명령을 사용할 필요가 없습니다. 대신 누셸에는 내장 http get
명령이 있으며 JSON 데이터를 기본적으로 처리합니다. 예시:
http get https://api.github.com/repos/nushell/nushell/contributors | select login contributions
누셸 방식으로 생각하기
누셸은 많은 셸과 언어에서 개념을 차용합니다. 누셸의 많은 기능이 익숙하게 느껴질 것입니다.
하지만 Bash는 아닙니다
그러나 이 때문에 Bash(및 일반적으로 POSIX) 스타일 구문 중 일부는 누셸에서 작동하지 않는다는 것을 잊기 쉽습니다. 예를 들어, Bash에서는 다음과 같이 작성하는 것이 일반적입니다.
# >를 사용하여 리디렉션
echo "hello" > output.txt
# 그러나 test 명령을 사용하여 비교(보다 큼)
test 4 -gt 7
echo $?
# => 1
그러나 누셸에서는 >
가 비교를 위한 보다 큼 연산자로 사용됩니다. 이것은 현대 프로그래밍 기대치와 더 일치합니다.
4 > 10
# => false
>
가 연산자이므로 누셸에서 파일로의 리디렉션은 콘텐츠 저장 전용 파이프라인 명령인 save
를 통해 처리됩니다.
"hello" | save output.txt
누셸 방식으로 생각하기
Bash에서 오신 분들을 위해 장에서 일반적인 Bash 사용법과 누셸에서 해당 작업을 수행하는 방법에 대한 목록을 정리했습니다.
암시적 반환
다른 셸에서 온 사용자는 echo
명령에 매우 익숙할 것입니다. 누셸의 echo
는 처음에는 동일하게 보일 수 있지만 매우 다릅니다.
먼저, 다음 출력이 Bash와 누셸(심지어 PowerShell과 Fish에서도)에서 어떻게 동일하게 보이는지 확인하십시오.
echo "Hello, World"
# => Hello, World
그러나 다른 셸이 Hello, World
를 _표준 출력_으로 직접 보내는 반면, 누셸의 echo
는 단순히 _값을 반환_합니다. 그런 다음 누셸은 명령 또는 더 기술적으로는 _표현식_의 반환 값을 _렌더링_합니다.
더 중요한 것은 누셸이 표현식의 값을 _암시적으로 반환_한다는 것입니다. 이것은 여러 면에서 PowerShell이나 Rust와 유사합니다.
팁
표현식은 파이프라인 이상일 수 있습니다. 사용자 지정 명령(많은 언어의 함수와 유사하지만 나중 장에서 더 자세히 다룰 것입니다)조차도 자동으로 마지막 값을 암시적으로 _반환_합니다. 값을 반환하기 위해 echo
나 심지어 return
명령도 필요하지 않습니다. 그냥 _발생_합니다.
즉, 문자열 _"Hello, World"_와 echo "Hello, World"
의 출력 값은 동일합니다.
"Hello, World" == (echo "Hello, World")
# => true
다음은 사용자 지정 명령 정의가 있는 또 다른 예입니다.
def latest-file [] {
ls | sort-by modified | last
}
해당 파이프라인의 출력("값")은 latest-file
사용자 지정 명령의 _반환 값_이 됩니다.
누셸 방식으로 생각하기
echo <something>
을 작성할 수 있는 대부분의 곳에서 누셸에서는 대신 <something>
만 작성하면 됩니다.
표현식당 단일 반환 값
표현식은 단일 값만 반환할 수 있다는 것을 이해하는 것이 중요합니다. 표현식 내에 여러 하위 표현식이 있는 경우 마지막 값만 반환됩니다.
일반적인 실수는 다음과 같은 사용자 지정 명령 정의를 작성하는 것입니다.
def latest-file [] {
echo "Returning the last file"
ls | sort-by modified | last
}
latest-file
새로운 사용자는 다음을 기대할 수 있습니다.
- 2행에서 "Returning the last file" 출력
- 3행에서 파일 반환/출력
그러나 echo
가 **값을 반환**한다는 것을 기억하십시오. 마지막 값만 반환되므로 2행의 _값_은 버려집니다. 3행에서 파일만 반환됩니다.
첫 번째 줄이 _표시_되도록 하려면 print
명령을 사용하십시오.
def latest-file [] {
print "Returning last file"
ls | sort-by modified | last
}
또한 다음을 비교하십시오.
40; 50; 60
위의 모든 경우:
- 첫 번째 값은 정수 40으로 평가되지만 반환되지 않습니다.
- 두 번째 값은 정수 50으로 평가되지만 반환되지 않습니다.
- 세 번째 값은 정수 60으로 평가되며, 마지막 값이므로 반환되어 표시(렌더링)됩니다.
누셸 방식으로 생각하기
예기치 않은 결과를 디버깅할 때 다음을 주의하십시오.
- 하위 표현식(예: 명령 또는 파이프라인)...
- ... (비-
null
) 값을 출력하는... - ... 해당 값이 부모 표현식에서 반환되지 않는 경우.
이것들은 코드에서 문제의 원인이 될 수 있습니다.
모든 명령은 값을 반환합니다
일부 언어에는 값을 반환하지 않는 "문"이라는 개념이 있습니다. 누셸에는 없습니다.
누셸에서는 **모든 명령이 값을 반환**합니다. 해당 값이 null
( nothing
유형)인 경우에도 마찬가지입니다. 다음 여러 줄 표현식을 고려하십시오.
let p = 7
print $p
$p * 6
- 1행: 정수 7이
$p
에 할당되지만let
명령 자체의 반환 값은null
입니다. 그러나 표현식의 마지막 값이 아니므로 표시되지 않습니다. - 2행:
print
명령 자체의 반환 값은null
이지만print
명령은 해당 인수($p
, 즉 7)를 _표시_하도록 강제합니다. 1행과 마찬가지로 이것이 표현식의 마지막 값이 아니므로null
반환 값은 버려집니다. - 3행: 정수 값 42로 평가됩니다. 표현식의 마지막 값이므로 이것이 반환 결과이며 표시(렌더링)됩니다.
누셸 방식으로 생각하기
일반적인 명령의 출력 유형에 익숙해지면 간단한 명령을 함께 결합하여 복잡한 결과를 얻는 방법을 이해하는 데 도움이 됩니다.
help <command>
는 누셸의 각 명령에 대한 서명(출력 유형 포함)을 표시합니다.
누셸을 컴파일된 언어로 생각하십시오
누셸에서는 코드를 실행할 때 정확히 두 개의 별도 상위 수준 단계가 있습니다.
- 1단계(파서): 전체 소스 코드 구문 분석
- 2단계(엔진): 전체 소스 코드 평가
누셸의 구문 분석 단계를 Rust 또는 C++와 같은 정적 언어의 _컴파일_로 생각하는 것이 유용할 수 있습니다. 즉, 2단계에서 평가될 모든 코드는 구문 분석 단계 중에 **알려지고 사용 가능**해야 합니다.
중요
그러나 이것은 또한 누셸이 현재 Bash나 Python과 같은 동적 언어와 같이 eval
구문을 지원할 수 없음을 의미합니다.
정적 구문 분석에 기반한 기능
반면에 구문 분석의 정적 결과는 다음과 같은 누셸의 REPL의 많은 기능에 대한 핵심입니다.
- 정확하고 표현력 있는 오류 메시지
- 오류 조건의 조기 및 강력한 감지를 위한 의미 분석
- IDE 통합
- 유형 시스템
- 모듈 시스템
- 완성
- 사용자 지정 명령 인수 구문 분석
- 구문 강조
- 실시간 오류 강조
- 프로파일링 및 디버깅 명령
- (미래) 서식 지정
- (미래) 더 빠른 실행을 위해 "컴파일된" IR(중간 표현) 결과 저장
제한 사항
누셸의 정적 특성은 eval
을 사용할 수 있는 언어에서 온 사용자에게 종종 혼란을 야기합니다.
간단한 두 줄 파일을 고려하십시오.
<line1 code>
<line2 code>
- 구문 분석:
- 1행이 구문 분석됩니다.
- 2행이 구문 분석됩니다.
- 구문 분석이 성공하면 평가:
- 1행이 평가됩니다.
- 2행이 평가됩니다.
이것은 다음 예제가 누셸에서 단일 표현식(예: 스크립트)으로 실행될 수 없는 이유를 설명하는 데 도움이 됩니다.
노트
다음 예제에서는 source
명령을 사용하지만, use
, overlay use
, hide
또는 source-env
와 같이 누셸 소스 코드를 구문 분석하는 다른 명령에도 유사한 결론이 적용됩니다.
예제: 동적으로 소스 생성
이 시나리오를 고려하십시오.
"print Hello" | save output.nu
source output.nu
# => Error: nu::parser::sourced_file_not_found
# =>
# => × File not found
# => ╭─[entry #5:2:8]
# => 1 │ "print Hello" | save output.nu
# => 2 │ source output.nu
# => · ────┬────
# => · ╰── File not found: output.nu
# => ╰────
# => help: sourced files need to be available before your script is run
이것은 문제가 있습니다. 왜냐하면:
- 1행은 구문 분석되지만 평가되지 않습니다. 즉,
output.nu
는 구문 분석 단계에서는 생성되지 않고 평가 중에만 생성됩니다. - 2행이 구문 분석됩니다.
source
는 파서 키워드이므로 소스 파일의 확인은 구문 분석(1단계) 중에 시도됩니다. 그러나output.nu
는 아직 존재하지도 않습니다! 존재하는 경우에도 올바른 파일이 아닐 수 있습니다! 이로 인해 오류가 발생합니다.
노트
**REPL**에서 두 개의 별도 줄로 입력하면 첫 번째 줄이 구문 분석 및 평가된 다음 두 번째 줄이 구문 분석 및 평가되므로 작동합니다.
제한은 스크립트, 블록, 클로저 또는 다른 표현식의 일부일 수 있는 단일 표현식으로 함께 구문 분석될 때만 발생합니다.
자세한 내용은 _"누셸 코드가 실행되는 방법"_의 REPL 섹션을 참조하십시오.
예제: 동적으로 소싱할 파일 이름 만들기
다른 셸에서 올 때 흔히 볼 수 있는 또 다른 시나리오는 소싱될 파일 이름을 동적으로 만들려고 시도하는 것입니다.
let my_path = "~/nushell-files"
source $"($my_path)/common.nu"
# => Error:
# => × Error: nu::shell::not_a_constant
# => │
# => │ × Not a constant.
# => │ ╭─[entry #6:2:11]
# => │ 1 │ let my_path = "~/nushell-files"
# => │ 2 │ source $"($my_path)/common.nu"
# => │ · ────┬───
# => │ · ╰── Value is not a parse-time constant
# => │ ╰────
# => │ help: Only a subset of expressions are allowed constants during parsing. Try using the 'const' command or typing the value literally.
# => │
# => ╭─[entry #6:2:8]
# => 1 │ let my_path = "~/nushell-files"
# => 2 │ source $"($my_path)/common.nu"
# => · ───────────┬───────────
# => · ╰── Encountered error during parse-time evaluation
# => ╰────
let
할당은 평가될 때까지 확인되지 않으므로 파서 키워드 source
는 변수가 전달되면 구문 분석 중에 실패합니다.
Rust 및 C++ 비교
위 코드가 C++와 같은 일반적인 컴파일 언어로 작성되었다고 상상해 보십시오.
#include <string>
std::string my_path("foo");
#include <my_path + "/common.h">
또는 Rust
let my_path = "foo";
use format!("{}::common", my_path);
이러한 언어 중 하나에서 간단한 프로그램을 작성해 본 적이 있다면 이러한 예제가 해당 언어에서 유효하지 않다는 것을 알 수 있습니다. 누셸과 마찬가지로 컴파일된 언어는 모든 소스 코드 파일이 컴파일러에 미리 준비되고 사용 가능해야 합니다.
참조
오류 메시지에서 언급했듯이 my_path
가 상수로 정의될 수 있는 경우 상수는 구문 분석 중에 확인될 수 있으므로(그리고 확인되므로) 작동할 수 있습니다.
const my_path = "~/nushell-files"
source $"($my_path)/common.nu"
자세한 내용은 구문 분석 시간 상수 평가를 참조하십시오.
예제: 다른 디렉터리로 변경(cd
)하고 파일 source
하기
하나 더 있습니다. 다른 디렉터리로 변경한 다음 해당 디렉터리에서 파일을 source
하려고 시도합니다.
if ('spam/foo.nu' | path exists) {
cd spam
source-env foo.nu
}
누셸의 구문 분석/평가 단계에 대해 다룬 내용을 바탕으로 해당 예제의 문제를 찾아보십시오.
해결책
3행에서 구문 분석 중에 source-env
가 foo.nu
를 구문 분석하려고 시도합니다. 그러나 cd
는 평가될 때까지 발생하지 않습니다. 이로 인해 파일이 현재 디렉터리에서 발견되지 않으므로 구문 분석 시간 오류가 발생합니다.
이 문제를 해결하려면 소싱할 파일의 전체 경로를 사용하기만 하면 됩니다.
source-env spam/foo.nu
요약
중요
이 섹션에 대한 자세한 내용은 누셸 코드가 실행되는 방법을 참조하십시오.
누셸 방식으로 생각하기
누셸은 각 표현식 또는 파일에 대해 단일 구문 분석 단계를 사용하도록 설계되었습니다. 이 구문 분석 단계는 평가 전에 발생하며 평가와는 별개입니다. 이것이 누셸의 많은 기능을 가능하게 하지만, 사용자가 그것이 만드는 제한 사항을 이해해야 한다는 것을 의미하기도 합니다.
변수는 기본적으로 불변입니다
다른 언어에서 올 때 흔히 놀라는 또 다른 점은 누셸 변수가 기본적으로 불변이라는 것입니다. 누셸에는 선택적 가변 변수가 있지만 누셸의 많은 명령은 불변성을 요구하는 함수형 프로그래밍 스타일을 기반으로 합니다.
불변 변수는 또한 여러 값을 스레드를 사용하여 병렬로 작업할 수 있는 누셸의 par-each
명령의 핵심입니다.
자세한 내용은 불변 변수 및 가변 변수와 불변 변수 중에서 선택을 참조하십시오.
누셸 방식으로 생각하기
가변 변수에 의존하는 데 익숙하다면 더 기능적인 스타일로 코딩하는 방법을 다시 배우는 데 시간이 걸릴 수 있습니다. 누셸에는 불변 변수와 함께 작동하고 불변 변수에서 작동하는 많은 기능적 기능과 명령이 있습니다. 그것들을 배우면 더 누셸 관용적인 스타일로 코드를 작성하는 데 도움이 될 것입니다.
좋은 보너스는 par-each
를 사용하여 코드의 일부를 병렬로 실행하여 얻을 수 있는 성능 향상입니다.
누셸의 환경은 범위가 지정됩니다
누셸은 컴파일된 언어에서 여러 디자인 단서를 가져옵니다. 그러한 단서 중 하나는 언어가 전역 가변 상태를 피해야 한다는 것입니다. 셸은 일반적으로 환경을 업데이트하기 위해 전역 변형을 사용했지만 누셸은 이 접근 방식을 피하려고 합니다.
누셸에서 블록은 자체 환경을 제어합니다. 환경에 대한 변경 사항은 발생하는 블록으로 범위가 지정됩니다.
실제로 이것은 (한 가지 예일 뿐이지만) 하위 디렉터리로 작업하기 위한 더 간결한 코드를 작성할 수 있게 해줍니다. 다음은 현재 디렉터리의 각 하위 프로젝트를 빌드하는 예입니다.
ls | each { |row|
cd $row.name
make
}
cd
명령은 PWD
환경 변수를 변경하지만 이 변수 변경은 블록이 끝난 후에도 유지되지 않습니다. 이를 통해 각 반복은 현재 디렉터리에서 시작하여 다음 하위 디렉터리로 들어갈 수 있습니다.
범위가 지정된 환경을 사용하면 명령을 더 예측 가능하고 읽기 쉽게 만들고, 때가 되면 디버깅하기 더 쉬워집니다. 이것은 또한 위에서 논의한 par-each
명령의 핵심 기능이기도 합니다.
누셸은 또한 한 번에 환경에 대한 여러 업데이트를 로드하는 편리한 방법으로 load-env
와 같은 도우미 명령을 제공합니다.
참조
노트
def --env
는 이 규칙의 예외입니다. 부모의 환경을 변경하는 명령을 만들 수 있습니다.
누셸 방식으로 생각하기
범위가 지정된 환경을 사용하여 더 간결한 스크립트를 작성하고 불필요하거나 원치 않는 전역 환경 변형을 방지하십시오.