#TIL 윈도우 터미널(windows terminal)에서 git bash 사용하기

마이크로소프트에서 만든 윈도우 터미널(windows terminal)은 셸(shell)을 호스팅하는 애플리케이션(application)이다. 즉 이 애플리케이션을 사용해 cmd, bash, powershell 등을 띄울 수 있다. 화면 분할도 지원해서 그동안 사용하던 ConEmu를 버리고 윈도우 터미널을 사용하고 있다.

C:\> winget install --id=Microsoft.WindowsTerminal -e

윈도우 패키지 매니저인 winget을 사용해 간단히 설치할 수 있다.

Win+R wt 로 실행할 수 있다. 실행 파일 이름이 wt.exe 인 걸 몰라서 한참 찾았다.

이제 git bash를 세팅할 차례다. C-, 를 눌러 설정창을 연 후 ‘+ 새 프로필 추가’ 를 클릭한다.

  • 명령줄: “C:\git-sdk-64\git-cmd.exe” –no-cd –command=usr/bin/bash.exe -l -i
  • 시작 디렉터리: %USERPROFILE%

이제 윈도우 터미널에서도 git bash를 사용할 수 있다.


#TIL #elixirlang guard 절을 사용해 case 문에서 여러 조건을 검사

case val do
  200 -> true
  404 -> true
  _ -> false
end

case 문에서 200 이거나 404일 때, 검사를 한 번에 하려면 어떻게 하면 될까?

case val do
  n when n in [200, 400] -> true
  _ -> false
end
case val do
  n when n == 200 or n == 400 -> true
  _ -> false
end

when 키워드로 가드(guard)를 사용하면 된다. 다만 이걸 쓰기 위해서는 심볼을 바인딩하고 이 심볼을 when 으로 검사해야 한다. 위 코드에서 바인딩한 심볼이 n 이다. or 연산자를 써도 되고 in 연산자를 써도 된다.

참고


#TIL 환경 변수를 유지한 채 sudo가 필요할 때

사내 linux 가상 머신 인스턴스에 gitlab runner를 설치하려고 했을 때였던 걸로 기억한다.

curl -L "https://packages.gitlab.com/install/repositories/runner/gitlab-runner/script.deb.sh" \
| sudo bash

gitlab 저장소를 추가한다. 이렇게 해야 이후에 업데이트도 쉽게 따라갈 수 있다. sudo apt-get update 명령만 내리면 최신 버전 정보를 가져오기 때문이다.

하지만 저 명령이 이상하게 동작하지 않는다. 특정 단계에서 시간이 오래 걸리며 시간 초과 에러가 난다. 스크립트 파일을 직접 다운로드 받아서 어디서 문제가 나는지 하나씩 실행해봤다. 아무런 문제 없이 잘 실행된다. 과연 뭐가 문제일까?

환경 변수 문제였다. 프록시 서버를 통해 인터넷 접근이 가능했는데, 그 설정을 https_proxy, http_proxy 환경 변수로 하고 있었다. sudo bash 명령은 환경 변수 유지 없이 superuser로 bash를 실행한다. 그래서 프록시 서버 세팅이 안 되니 인터넷에 접근해 파일을 받아오는 스크립트에서 시간 초과 에러가 발생한다.

curl -L "https://packages.gitlab.com/install/repositories/runner/gitlab-runner/script.deb.sh" \
| sudo -E bash

-E 옵션을 사용하면 환경 변수를 유지한다. sudo bashsudo -E bash 로 바꿔주면 잘 동작한다.

링크


#TIL macOS에서 키 반복 입력 활성화하기

defaults write -g ApplePressAndHoldEnabled -bool false

Android Studio IDE의 vim 플러그인 ideavim을 설치해서 사용하다 보니 키 반복 입력을 활성화하는 방법을 알려주더라. 친절하다. 터미널을 열어서 위 명령어를 입력하면 된다.

링크


#TIL 에어팟(airpod) 자동 연결 옵션 변경

에어팟(AirPods, 2019) 2세대‘를 잘 사용하고 있다. iOS, iPadOS 14 이후로 지원되는 자동 전환을 켜서 사용하고 있었다. macOS도 지원돼서 기기간 연결이 잘 넘어간다. macOS에서 음악을 듣다가 별도 작업 없이 iOS로 듀오링고를 켜서 영어 공부를 하면 에어팟이 iOS에 연결이 되는 식으로 동작한다.

하지만 대부분의 부모가 그렇듯이 내 것인 줄 알았던 아이패드(iPad)가 자녀 교육용으로 넘어갈 때, 문제가 생긴다. 한창 코로나19 때문에 원격 수업을 많이 할 때, 문제가 발생했다. 화장실에 가려고 딸기부엉이 방 근처로 가면 원격 수업을 하는 아이패드에 내 에어팟이 연결되곤 했다. 방에선 갑자기 아이패드에서 소리가 안 들린다며 나를 찾기 시작한다.

/ddiary/assets/2022-06-25-til-how-to-stop-airpods-from-auto-switching-00.jpg

에어팟이 연결된 상태에서 블루투스(bluetooth) 설정으로 들어간다. 연결된 에어팟 옆에 보이는 i 버튼을 누르면 상세 설정에 들어갈 수 있다. 거기서 연결 메뉴를 볼 수 있는데, 자동 연결이 아닌 마지막으로 연결된 기기일 때만 연결되는 옵션을 선택한다. 이제는 소유권이 애매해진 아이패드에서는 자동연결을 끄고 사용 하고 있다.


#TIL macOS에서 steam 자동 실행 끄기

/ddiary/assets/2022-05-01-til-disable-auto-start-on-mac-00.jpg

시스템 환경설정 > 사용자 및 그룹 > 로그인 항목

목록에 보이면 클릭한 후 밑에 - 버튼을 눌러서 제거하면 된다.

macOS steam 프로그램에 환경 설정 > 인터페이스 > 컴퓨터를 시작할 때 Steam 자동 실행 옵션이 있지만 동작하지 않는다. Disable Steam auto start on Mac 글을 참고해서 자동 실행을 껐다.


#TIL #macOS 업그레이드 하기 전 USB로 부팅할 수 있는 안전 장치는 마련하자

/ddiary/assets/2022-04-10-til-macos-startup-security-utility-00.jpg

macOS 업데이트가 실패했다. 복구 모드로 와이파이를 잡고 운영체제를 다운로드받아 설치하는 것도 실패했다. 그냥 로컬에 저장된 복구 OS를 설치할걸. 그걸로 다시 시도해본다. 또 실패했다. USB에 macOS를 설치해 이걸로 부팅해서 설치해야겠다. 보안 칩이라니 있으니 좋겠지 하며 흘려들었던 T2 보안 칩이 부팅을 막는다.

한번 고생을 한 후에는 macOS 업데이트를 할 때, 항상 USB로 부팅할 수 있게 만들어 놓는다.

  1. Mac을 켠 다음 Apple 로고가 표시되면 곧바로 command(⌘)-R 키를 길게 누릅니다. Mac이 macOS 복구로 시동됩니다.
  2. 암호를 알고 있는 사용자를 선택하라는 메시지가 표시되면 사용자를 선택하고 ‘다음’을 클릭한 후 관리자 암호를 입력합니다.
  3. macOS 유틸리티 윈도우가 나타나면 메뉴 막대에서 유틸리티 > 시동 보안 유틸리티를 선택합니다.
  4. 인증하라는 메시지가 나타나면 ‘macOS 암호 입력’을 클릭한 다음 관리자 계정을 선택하고 해당 암호를 입력합니다.

- Apple T2 보안 칩이 탑재된 Mac의 시동 보안 유틸리티에 관하여

외부 또는 제거 가능한 미디어에서 시동 허용 라디오 버튼을 체크하면 된다.


#TIL #elixirlang 이 편한 match?/2

iex> match?(%{a: _}, %{a: 1, b: 2})
true
iex> match?(%{c: _}, %{a: 1, b: 2})
false

match?/2 매크로는 패턴에 매칭(matching)하는지를 boolean으로 리턴한다.

iex> match?(%{a: c}, %{a: 1, b: 2})
warning: variable "c" is unused (if the variable is not meant to be used, prefix it with an underscore)
  iex:17
true
iex> c
c
** (CompileError) iex:18: undefined function c/0

매크로 안에서 심볼에 값을 바인딩 되지 않는다. context를 공유하지 않는 macro hygiene이 적용된다.

iex> match?(%{a: x} when x > 2, %{a: 4, c: 2})
true
iex> match?(%{a: x} when x < 2, %{a: 4, c: 2})
false

안에서 쓸 수 없을 것 같은 가드 절(guard clause)도 사용할 수 있다.

그래서? 단독으로 사용할 때는 시큰둥해진다. 여기에 다른 함수를 끼얹는다면?

iex> list = [a: 1, b: 2, a: 3]
[a: 1, b: 2, a: 3]
iex> Enum.filter(list, &match?({:a, _}, &1))
[a: 1, a: 3]
iex> Enum.any?(list, &match?({_, 3}, &1))
true
iex> Enum.all?(list, &match?({_, 3}, &1))
false
iex> Enum.find(list, &match?({:a, x} when x > 2, &1))
{:a, 3}

Enum.filter/2, Enum.any?/2, Enum.all?/2, Enum.find/3술어(predicate)를 인자로 받는 모든 함수에 편하게 쓸 수 있다.


#TIL 닌텐도 스위치에 저장된 스크린샷 옮기기

닌텐도 스위치와 컴퓨터를 USB 케이블로 연결한다. 바로 인식하지 않는데, 별도 프로그램이 필요하다. 닌텐도 스위치 OS가 안드로이드인가보다. Android File Transfer 프로그램을 설치해야 한다.

닌텐도 스위치에서 설정 > 데이터 관리 > 화면 사진과 동영상 관리 > USB를 연결하여 컴퓨터에 복사 메뉴를 선택한다. 그 후 설치한 Android File Transfer 프로그램을 실행하면 닌텐도 스위치 디렉터리가 보인다.

macOS에서 테스트했다. windows에서도 잘 동작하지 싶다.


#TIL wifi 접속 QR코드 만들기

/ddiary/assets/2022-02-13-til-wifi-qr-code-00.jpg

apple 단축어 앱을 사용하면 wifi 접속 QR 코드를 만들 수 있다.

  1. 텍스트: WIFI:S:와이파이이름;T:WPA;P:패스워드;;
  2. QR 코드 생성
  3. 훑어보기

QR 코드를 찍으면 wifi 접속할 것인지 묻고 접속한다. 아이폰, 갤럭시 상관없이 동작한다. 둘 다 동작한다니 표준 스팩 같은 게 있는 게 아닐까? 표준 문서는 못 찾았다. 대신 email, vCard, sms, facetime, map, calendar event, wifi network 설정이 적힌 barcode 스캐닝 라이브러리 ZXing의 Barcode Contents 문서를 찾았다.

만드는 법을 설명한 youtube를 보고 따라서 액자를 만들어봤다. 액자가 구려서 안 예쁘다. airbnb 숙소를 운영하는 것도 아니어서 사진만 찍고 폐기했다. 친척이 놀러 왔을 때, wifi 비번을 물어보면 apple 단축어 앱으로 만든 QR 코드를 보여주는 거로 충분할 것 같다. 그래도 찍기만 했던 QR 코드를 만드니 재미있었다.

PS: 사진에 찍힌 QR 코드는 와이파이 SSID와 패스워드는 임의로 만든 거니 스캔해도 쓸모가 없다.

참고


#TIL #vim OS 클립보드를 사용해 복사하고 붙이기

"+p

커맨드로 OS 클립보드 내용을 붙일 수 있다.

커맨드를 분해하면 다음과 같다.

    • 레지스터 사용
  • +
    • OS 클립보드 사용
  • p
    • 붙이기(paste) 동작. 복사하려면 y 키를 사용하면 된다.
set clipboard=unnamed

이 세팅으로 OS 클립보드를 같이 사용할 수 있지만 기본 세팅으로 놔두고 쓰고 있다. emacs는 OS 클립보드를 구분하지 않는데, 이참에 vim도 바꿀까 싶기는 하다.

참고


#TIL #elixirlang 1.13.0 이전 elixir에는 없는 map 필터 함수

map = for i <- 1..100, into: %{}, do: {i, Enum.random([false, true])}

키는 숫자, 값은 boolean인 map을 만든다. %{1 => true, 2 => false} 이런 식으로 만들어진 map이다.

value가 true인 key, value 쌍을 뽑아서 map을 만들려면 어떻게 하면 될까? Enum.filter/2 함수를 써서 value가 true인 key, value 쌍을 걸러내 보자.

map |> Enum.filter(&match?({_, true}, &1))
[
  {12, true},
  {38, true},
  {93, true},
  {53, true},
  {46, true},
  {23, true},
  {80, true},
  {96, true},
  {75, ...},
  {...},
  ...
]

Enum.filter/2 리턴 값이 list라서 map이 아닌 list가 나온다.

map |> Enum.filter(&match?({_, true}, &1)) |> Map.new()
%{
  48 => true,
  62 => true,
  39 => true,
  83 => true,
  63 => true,
  34 => true,
  68 => true,
  ...
}

map에 filter를 적용하려면 list를 리턴하는 Enum.filter/2 함수 다음에 Map.new/1 함수를 사용해 list를 map으로 만들어야 한다.

erlang도 이럴까? erlang 라이브러리에는 둘러가지 않고 한방에 가는 maps.filter/2 함수가 있다.

:maps.filter(fn _, value -> value end, map)
%{
  48 => true,
  62 => true,
  39 => true,
  83 => true,
  63 => true,
  34 => true,
  ...
}

pipe 연산자에 친화적이지 않다. map을 첫 번째 인자로 받지 않아 마음에 안 들지만 잘 동작한다. 중간에 list를 생성하지 않아 훨씬 더 빠르다.

다행히 elixir 1.13.0에 추가된다. Map.filter/2 함수뿐만 아니라 Map.map/2 함수도 추가된다.

버전업이 곤란하면 구현 함수를 복사해서 사용하면 된다. 단순 :maps.filter/2 함수 호출인가 싶어서 코드를 살펴봤다. :maps.iterator/1, :maps.next/1 함수로 반복자(iterator)를 만들어서 요소들을 차례로 순회하며 filter 함수를 호출한다. 생각해보니 map 요소에 접근하는 함수 시그니처(signature)도 다르다. elixir에서는 tuple을 인자로 받는데, erlang에선 2개의 인자를 받는다. 그래서 직접 순회하며 filter 함수를 호출하게 했다.

참고


#TIL #elixirlang 패턴 매칭으로 map의 value 가져오기

defp ask_and_schedule(producers, from) do
  case producers do
    %{^from => {pending, interval}} ->
    # ...
    %{} ->
    # ...
  end
end

GenStage 코드를 보다가 발견했다. Map.get(producers, from) 혹은 producers[from] 으로 값을 가져오는 게 아니라 패턴 매칭을 사용한다.

Map.get/3 함수보다 빠를까?

defmodule Map do
  def get(map, key, default \\ nil) do
    case map do
      %{^key => value} ->
        value

      %{} ->
        default

      other ->
        :erlang.error({:badmap, other}, [map, key, default])
    end
  end
end

당연. 빠를 수밖에 없다. Map.get/3 함수를 패턴 매칭으로 구현했다. 패턴 매칭이 더 근본이다. 하지만 크게 의미있는 속도 차이는 없으니 가독성을 보고 선택하면 된다.


#TIL google sheets 특정 개월 전후 계산 함수

EDATE(DATE(1969,7,20), 1)
EDATE(B1,-3)

인자로 시작일과 개월 수를 받는다. 달마다 기록하는 항목이 있을 때, 편리하게 사용할 수 있다.

PS: 앞에 e는 무슨 의미일까?


#TIL 윈도우 터미널 프로그램 ConEmu에서 UTF-8 한글 출력

UTF-8 인코딩으로 출력하는 한글이 깨져서 한참 찾았다. locale 명령으로 인코딩 설정을 봐도 이상한 게 없다. 아~ windows에서 실행하는 거였지.

Settings... > Startup > Environment
chcp 65001 

ConEnum 설정에서 환경 변수를 세팅하는 곳이 있다. 추가하니 잘 나온다. 이런 설정이 나만 필요한 게 아니었는지 Environment에 이런 걸 설정할 수 있다며 든 예제 중에 chcp 프로그램을 사용한 인코딩 변경이 있다.


#TIL #elixirlang iex에서 pid 만드는 법

프로그래밍할 때는 숫자를 인자로 넣어서 pid(프로세스 아이디)를 만들 일이 없지만, elixir 대화형 셸(iex, elixir’s interactive shell)을 사용해 프로세스에 직접 메시지를 보낼 때, 가끔 사용한다.

#PID<0.101.0>

이런 식으로 출력되는 로그를 보고 pid를 만들어서 메시지를 보내야 하는데, pid를 어떻게 만들면 될까?

iex> pid("0.101.0")
#PID<0.101.0>
iex> pid(0, 101, 0)
#PID<0.101.0>
iex> :c.pid(0, 101, 0)
#PID<0.101.0>

IEx.Helpers.pid/1, IEx.Helpers.pid/3 함수를 사용하거나 :c.pid/3 erlang 함수를 사용하면 된다. common interface module 이름을 c 로 짓다니 과감하시다.

참고


#TIL #elixirlang EUC-KR 인코딩을 utf-8 인코딩으로 변환

때가 어느 때인데, EUC-KR 인코딩을 UTF-8 인코딩으로 변환하는 걸 찾아봐야 하는가? 대부분 UTF-8 인코딩을 사용하고 있지만 많은 레거시 시스템은 아직도 EUC-KR 인코딩을 사용하고 있다.

:iconv.convert("euc-kr", "utf-8", content)

iconv 라이브러리를 설치 후 :iconv.convert/3 함수를 사용한다. iconv 프로그램을 사용하기 때문에 시스템에 설치되어 있어야 한다. macOS에 설치되어 있어서 따로 설치는 하지 않았다. windows에서 프로그램을 돌린다면? 당연히 설치해야 한다.

참고


#TIL #elixirlang is_atom(nil) 값은?

is_atom(nil)

값은 뭘까? is_atom/1 함수는 인자가 atom 일 때, true를 리턴하고 아니면 false를 리턴한다.

true

true가 나온다. nil은 atom이기 때문이다.

iex> is_atom(true)
true
iex> is_atom(false)
true

true와 false도 마찬가지다. nil, true, false 모두 atom이다.

iex> nil == :nil
true
iex> true == :true
true
iex> false == :false
true

Elixir allows you to skip the leading : for the atoms false, true and nil.

- Basic types

nil, true, false는 atom인데도 : 문자를 빼고 쓸 수 있게 편의 문법(syntactic sugar)으로 지원한다.

atom이기 때문에 guard를 쓸 때, 주의해야 한다.

def some_function(param) when is_atom(param), do: # do somthing

이런 함수를 만들면 param으로 nil과 true, false도 들어올 수 있다. 만약 이걸 의도하지 않았다면

def some_function(param) when is_atom(param) and
  not is_boolean(param) and
  not is_nil(param),
  do: # do somthing

이렇게 boolean과 nil을 guard로 거르게 한다던지

def some_function(param) when is_boolean(param) , do: # nothing
def some_function(param) when is_nil(param), do: # nothing
def some_function(param) when is_atom(param), do: # do somthing

먼저 nil과 boolean 일때 매칭해서 처리하도록 코드를 짜야 한다.

참고


#TIL #elixirlang 구조체(struct)를 jason을 사용해 인코딩하려면

csv로 저장하려고 했는데, jason 라이브러리를 사용하면 직렬화(serialization), 역직렬화(deserialization)가 편해서 json 포멧을 사용한다.

defmodule Data do
  defstruct [:a, :b, :c, :d]
end

Data 구조체를 만들고 이걸 json으로 인코딩해본다.

content = %Data{a: 1, b: 2, c: 3, d: 4}
Jason.encode!(content, pretty: true)
** (Protocol.UndefinedError) protocol Jason.Encoder not implemented for %Data{a: 1, b: 2, c: 3, d: 4} of type Data (a struct), Jason.Encoder protocol must always be explicitly implemented.

If you own the struct, you can derive the implementation specifying which fields should be encoded to JSON:

    @derive {Jason.Encoder, only: [....]}
    defstruct ...

It is also possible to encode all fields, although this should be used carefully to avoid accidentally leaking private information when new fields are added:

    @derive Jason.Encoder
    defstruct ...

Finally, if you don't own the struct you want to encode to JSON, you may use Protocol.derive/3 placed outside of any module:

    Protocol.derive(Jason.Encoder, NameOfTheStruct, only: [...])
    Protocol.derive(Jason.Encoder, NameOfTheStruct)
. This protocol is implemented for the following type(s): Any, Atom, BitString, Date, DateTime, Decimal, Float, Integer, Jason.Fragment, List, Map, NaiveDateTime, Time
    (jason) lib/jason.ex:150: Jason.encode!/2

Data 구조체에 대한 Jason.Encoder 프로토콜이 구현 안 됐다며 에러가 난다. 도움말이 너무 친절해 그대로 따라 하면 된다.

defimpl Jason.Encoder, for: Data do
  def encode(value, opts) do
    Jason.Encode.map(Map.take(value, [:a, :b, :c, :d]), opts)
  end
end

이렇게 직접 구현하던가

defmodule Data do
  @derive Jason.Encoder
  defstruct [:a, :b, :c, :d]
end

@derived 속성을 사용해 기본 구현을 사용한다고 알려주면 된다.

iex> Jason.encode!(content, pretty: true)
"{\n  \"a\": 1,\n  \"b\": 2,\n  \"c\": 3,\n  \"d\": 4\n}"

구조체도 json으로 인코딩이 잘 된다.

@derived 속성을 사용하면 __deriving__ 콜백 함수가 호출된다. :only, :except 옵션을 사용하지 않으면 __struct__ 키만 빼고 인코딩한다. 프로토콜을 구현할 때, __deriving__ 콜백을 구현해놓으면 사용하는 사람이 편하다.

encode.ex 파일 구현을 보면 Date, Time, NaiveDateTime, DateTime 구조체는 미리 정의해놨다. 사용자가 의도하지 않는 인코딩을 방지하려고 구조체를 인코딩하려고 하면 에러를 일으킨다. 의도한 게 맞다면 구현하기 쉽게 @derived 속성을 사용하게 구현을 해놨다. 의도하지 않은 사용은 방지하고 원한다면 쉽게 구현할 수 있게 디자인했다. 정석처럼 잘해놨다.


#TIL #vim 범위에 매크로 실행을 하고 싶다면

SomeModule.some_func(1, 2, 3)
SomeModule.some_func(1, 3, 4, 5)
SomeModule.some_func(1, 4, 5, 6, 7)
SomeModule.some_func(1, 5, 6, 7, 8, 9)
SomeModule.some_func(1, 6, 7, 8, 10, 11)

두 번째 인자를 싹 없애고 싶다. normal 모드에서 qq WdW 키를 입력한다.

SomeModule.some_func(1, 3)
SomeModule.some_func(1, 3, 4, 5)
SomeModule.some_func(1, 4, 5, 6, 7)
SomeModule.some_func(1, 5, 6, 7, 8, 9)
SomeModule.some_func(1, 6, 7, 8, 10, 11)

첫 번째 라인에는 없어졌다. 나머지 라인에도 적용하고 싶다. 라인을 바꿔가며 @q 키를 입력해서 위에서 입력한 q 매크로를 실행하면 된다.

:2,5normal @q

normal 명령어를 범위에 대고 실행해보자.

SomeModule.some_func(1, 3)
SomeModule.some_func(1, 4, 5)
SomeModule.some_func(1, 5, 6, 7)
SomeModule.some_func(1, 6, 7, 8, 9)
SomeModule.some_func(1, 7, 8, 10, 11)

짠! normal 명령어를 사용하면 범위에 대고 normal 명령을 적용할 수 있다. 매크로 실행뿐만 아니라 normal 명령어면 모두 가능하다. 위 예제에서 매크로를 만들지 않고 :%normal WdW 을 입력해도 똑같은 결과가 나온다. 복잡한 건 매크로를 만들어서 실행하고 간단한 건 바로 normal 명령어를 사용하면 된다.

참고