Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to detect if the shell is interactive and login? #1726

Open
balupton opened this issue Sep 30, 2023 · 19 comments
Open

How to detect if the shell is interactive and login? #1726

balupton opened this issue Sep 30, 2023 · 19 comments

Comments

@balupton
Copy link

balupton commented Sep 30, 2023

I've scanned the website and can't seem to find the answer to this. I've also searched for where I should post discussions, but the README doesn't mention anywhere, and CONTRIBUTING.md mentions a user group with no link.

in fish:

if status --is-login
	...
end

if status --is-interactive
	...
end

in nu:

if $nu.is-login {
	...
}

if $nu.is-interactive {
	...
}

in xonsh:

if $XONSH_LOGIN == True:
	...

if $XONSH_INTERACTIVE == True:
	...

My use case is adding Elvish support to https://github.com/bevry/dorothy which will integrate with Elvish by adding itself to the rc script, and loading different config for interactive and login.

@balupton
Copy link
Author

I'm guessing that it doesn't exist yet, and when it does exit it would be at $runtime:is-interactive and $runtime:is-loginhttps://elv.sh/ref/runtime.html

@krader1961
Copy link
Contributor

The Fish shell needs those tests because its config.fish script is loaded by every Fish shell; regardless of whether it is interactive or not. Elvish does not need the equivalent because its rc.elv script is only loaded if the shell is interactive. It is also unlikely that Elvish will ever distinguish between login and non-login interactive shells. So it is sufficient to simply add, without any conditional code, your initialization statements to rc.elv. Although, I personally prefer that utilities like Dorothy simply tell me what to add rather than munging my startup script. If you do decide to automatically see https://elv.sh/ref/runtime.html#$runtime:effective-rc-path and https://elv.sh/ref/runtime.html#$runtime:rc-path.

@balupton
Copy link
Author

balupton commented Oct 7, 2023

Okie, so getting the following:

# https://elv.sh/ref/command.html#rc-file
# https://elv.sh/ref/runtime.html
# https://github.com/elves/elvish/issues/1726
if ?(not-eq $runtime:effective-rc-path $nil) {
	...
}
Exception: Compilation error: variable $runtime:effective-rc-path not found
  [eval 1]:13:13: if ?(not-eq $runtime:effective-rc-path $nil) {

edit: nevermind, didn't realise I needed use runtime

@balupton
Copy link
Author

balupton commented Oct 7, 2023

Although, I personally prefer that utilities like Dorothy simply tell me what to add rather than munging my startup script.

That's the plan.

@krader1961
Copy link
Contributor

nevermind, didn't realise I needed use runtime)

That is a very common mistake. So common that it might be worthwhile to modify the error message to suggest to the user that they should use the module. However, that would make the error message extremely verbose and thus requires some thought regarding whether the verbosity is justified.

@krader1961
Copy link
Contributor

FWIW, I am a grey beard. I started programming in the 1970's and got acquainted with UNIX in the mid 1980's. Which means I was familiar with the concept of a "login" shell as a shell spawned by the getty program (or equiavalent) versus subsequent sub-shells. That distinction is no longer relevant and I am surprised that modern shells like Xonsh still make that distinction.

@balupton
Copy link
Author

balupton commented Oct 7, 2023

This seems the final head scratcher for me:

#!/usr/bin/env fish

echo 'theme='$E:DOROTHY_THEME

if ?(has-env DOROTHY_THEME_OVERRIDE) {
	set-env DOROTHY_THEME $E:DOROTHY_THEME_OVERRIDE
}

echo 'theme='$E:DOROTHY_THEME

if ?(and $true ?(has-env DOROTHY_THEME) ?(not-eq $E:DOROTHY_THEME 'system' '' $nil)) {
	if ?(test -f $E:DOROTHY'/user/themes/'$E:DOROTHY_THEME'.elv') {
		eval (cat $E:DOROTHY'/user/themes/'$E:DOROTHY_THEME'.elv' | slurp)
	} elif ?(test -f $E:DOROTHY'/themes/'$E:DOROTHY_THEME'.elv') {
		eval (cat $E:DOROTHY'/themes/'$E:DOROTHY_THEME'.elv' | slurp)
	} else {
		echo-style --warning='Dorothy theme ['$E:DOROTHY_THEME'] is not supported by this shell [elvish]' >/dev/stderr
	}
}

# @todo if no theme is defined, I don't know why this outputs:
# Dorothy theme [] is not supported by this shell [elvish]

Is outputting:

theme=starship
▶ $false
theme=
▶ $true
▶ $true
▶ $ok
Dorothy theme [] is not supported by this shell [elvish]

Which is baffling me as to why an empty or non-defined DOROTHY_THEME_OVERRIDE is causing DOROTHY_THEME to be set to it, and then why my sanity checks of and $true ... and not-eq ... are not preventing:

Dorothy theme [] is not supported by this shell [elvish]

I'm guessing this, and the $true and $false outputs have to do with my possibly/probably incorrect fix to this error of the following code by wrapping the if clause in ?(

if has-env DOROTHY_THEME_OVERRIDE {
	set-env DOROTHY_THEME $E:DOROTHY_THEME_OVERRIDE
}
Exception: Compilation error: if body must be lambda, found primary expression of type Bareword
  [eval 7]:5:12: if has-env DOROTHY_THEME_OVERRIDE {
Traceback:
  [eval 5]:16:1:
    eval (cat $E:DOROTHY'/sources/theme.elv' | slurp)
  [eval 1]:16:1:
    	eval (cat $E:DOROTHY'/sources/interactive.elv' | slurp)
  /Users/balupton/.config/elvish/rc.elv:1:1:
    eval (cat '/Users/balupton/.local/share/dorothy/init.elv' | slurp) # Dorothy

I tried to search for resources, but this was the only result: https://github.com/search?q=%22if+body+must+be+lambda%22&type=issues

@balupton
Copy link
Author

balupton commented Oct 7, 2023

Figured it out, needed ( ... ) instead of ?( ... ) for builtins

#!/usr/bin/env fish

if (has-env DOROTHY_THEME_OVERRIDE) {
	set-env DOROTHY_THEME $E:DOROTHY_THEME_OVERRIDE
}

if (and $true (has-env DOROTHY_THEME) (not-eq $E:DOROTHY_THEME 'system')) {
	if ?(test -f $E:DOROTHY'/user/themes/'$E:DOROTHY_THEME'.elv') {
		eval (cat $E:DOROTHY'/user/themes/'$E:DOROTHY_THEME'.elv' | slurp)
	} elif ?(test -f $E:DOROTHY'/themes/'$E:DOROTHY_THEME'.elv') {
		eval (cat $E:DOROTHY'/themes/'$E:DOROTHY_THEME'.elv' | slurp)
	} else {
		echo-style --warning='Dorothy theme ['$E:DOROTHY_THEME'] is not supported by this shell [elvish]' >/dev/stderr
	}
}

@krader1961
Copy link
Contributor

#!/usr/bin/env fish

I am going to assume that is a typo. :-)

if ?(has-env DOROTHY_THEME_OVERRIDE)

You have misunderstood the purpose of the ?(...) operator. It is intended to keep external commands that exit with a non-zero status from terminating the Elvish shell. See https://elv.sh/ref/language.html#exception-capture. It is not relevant to that example. f

@balupton
Copy link
Author

balupton commented Oct 7, 2023

Thank you. I also found https://elv.sh/learn/unique-semantics.html very useful.

One final thing, related to CI. I'm now trying to test the Dorothy integration with Elvish on CI, and getting:

Run # when given a file, elvish no longer loads its rc file, so we will load ourselves:
  # when given a file, elvish no longer loads its rc file, so we will load ourselves:
  eval (cat '/home/runner/.config/elvish/rc.elv' | slurp)
  # continue as normal
  command-exists dorothy
  echo-style --success='ok'
  shell: /usr/bin/elvish {0}
Exception: no such module: runtime
Traceback:
  [eval 2], line 2:
    use runtime
  [eval 1], line 1:
    eval (cat '/home/runner/.local/share/dorothy/init.elv' | slurp) # Dorothy
  /home/runner/work/_temp/7846f7c7-61f2-4796-989b-0f34db63505c, line 2:
    eval (cat '/home/runner/.config/elvish/rc.elv' | slurp)

https://github.com/bevry/dorothy/actions/runs/6439819213/job/17487942227#step:9:1


Prior to that, I had tried this with the same error:

Run # when given a file, elvish no longer loads its rc file, so we will load ourselves:
  # when given a file, elvish no longer loads its rc file, so we will load ourselves:
  use runtime
  eval (cat $runtime:rc-path | slurp)
  # continue as normal
  command-exists dorothy
  echo-style --success='ok'
  shell: /usr/bin/elvish {0}
Exception: no such module: runtime
/home/runner/work/_temp/d3ab71e9-bc6d-4aa4-9340-00b013d1a213, line 2: use runtime
Error: Process completed with exit code 2.

https://github.com/bevry/dorothy/actions/runs/6439804989/job/17487912535#step:9:2


It's installing elvish 0.17.0-1

Preparing to unpack .../elvish_0.17.0-1_amd64.deb ...
Unpacking elvish (0.17.0-1) ...
Setting up elvish (0.17.0-1) ...
Processing triggers for man-db (2.10.2-1) ...
NEEDRESTART-VER: 3.5
NEEDRESTART-KCUR: 6.2.0-1012-azure
NEEDRESTART-KEXP: 6.2.0-1012-azure
NEEDRESTART-KSTA: 1
The [elvish] utility was installed via [apt]
The [elvish] utility was installed. ✅

@balupton
Copy link
Author

balupton commented Oct 7, 2023

Will try using the prebuilt binaries instead.

@balupton
Copy link
Author

balupton commented Oct 7, 2023

Same situation was 0.19.2 prebuilt binary:

Run # when given a file, elvish no longer loads its rc file, so we will load ourselves:
  # when given a file, elvish no longer loads its rc file, so we will load ourselves:
  eval (cat '/home/runner/.config/elvish/rc.elv' | slurp)
  # continue as normal
  command-exists dorothy
  echo-style --success='ok'
  shell: /home/runner/.local/bin/elvish {0}
Exception: exec: "command-exists": executable file not found in $PATH
/home/runner/work/_temp/68ce339b-5cac-433a-b052-c0c13250e5e5:4:1: command-exists dorothy
Error: Process completed with exit code 2.

https://github.com/bevry/dorothy/actions/runs/6440000404/job/17488345034#step:9:1

@krader1961
Copy link
Contributor

The eval command creates a new namespace. It does not behave like you are used to with POSIX shells like Bash. See https://elv.sh/ref/builtin.html#eval. It should also be avoided. Rather than using eval use a relative import: https://elv.sh/ref/language.html#relative-imports.

if (and $true (has-env DOROTHY_THEME) (not-eq $E:DOROTHY_THEME 'system')) {

There is no need for $true in that statement.

Exception: exec: "command-exists": executable file not found in $PATH

Is command-exists a function you're defining?

It is unclear why you are loading rc.elv. That file would typically contain statements that don't make sense, and won't even work, in a non-interactive shell. For example, key bindings, command abbreviations, and REPL variable definitions. Here are just a couple of examples from my rc.elv:

# Arrange for [alt-e] and [alt-v] to edit the current command buffer using my
# prefered external editor.
set edit:insert:binding[Alt-e] = $interactive:external-edit-command~
set edit:insert:binding[Alt-v] = $interactive:external-edit-command~

# Populate the interactive REPL namespace with functions we want usable
# without requiring their namespace prefix; e.g., `ff` rather than `util:ff`.
edit:add-var cd~ $interactive:cd~
edit:add-var coff~ $util:coff~
edit:add-var con~ $util:con~

set edit:command-abbr['ds'] = 'try { pkill -x VLC } catch { }; pmset displaysleepnow'
set edit:command-abbr['gam'] = 'hub am -3'
set edit:command-abbr['gb'] = 'git branch'

It would be simpler to put whatever you're inserting into rc.elv into a different file that also includes your unit tests then simply execute that test script; e.g., elvish /path/to/test.elv.

@balupton
Copy link
Author

balupton commented Oct 8, 2023

The eval command creates a new namespace. It does not behave like you are used to with POSIX shells like Bash. See https://elv.sh/ref/builtin.html#eval. It should also be avoided. Rather than using eval use a relative import: https://elv.sh/ref/language.html#relative-imports.

I couldn't get relative imports working properly:

> elvish
Exception: no such module: ../../.local/share/dorothy/init.elv
/Users/balupton/.config/elvish/rc.elv:1:1: use '../../.local/share/dorothy/init.elv' # Dorothy
~/.local/share/dorothy> exit
13:04:48:/Users/balupton/.local/share/dorothy:master
> cat /Users/balupton/.config/elvish/../../.local/share/dorothy/init.elv
#!/usr/bin/env elvish
use runtime

# this should be consistent with:
# $DOROTHY/init.*
# $DOROTHY/commands/dorothy
if (not (has-env 'DOROTHY')) {
	set-env 'DOROTHY' $E:HOME'/.local/share/dorothy'
}

# https://elv.sh/ref/command.html#rc-file
# https://elv.sh/ref/runtime.html
# https://github.com/elves/elvish/issues/1726
if (not-eq $runtime:effective-rc-path $nil) {
	use './sources/login.elv'
	use './sources/interactive.elv'
}

Using a absolute path also did not work:

> elvish
Exception: no such module: /Users/balupton/.local/share/dorothy/init.elv
/Users/balupton/.config/elvish/rc.elv:1:1: use '/Users/balupton/.local/share/dorothy/init.elv' # Dorothy
~/.local/share/dorothy> cat /Users/balupton/.local/share/dorothy/init.elv
#!/usr/bin/env elvish
use runtime

# this should be consistent with:
# $DOROTHY/init.*
# $DOROTHY/commands/dorothy
if (not (has-env 'DOROTHY')) {
	set-env 'DOROTHY' $E:HOME'/.local/share/dorothy'
}

# https://elv.sh/ref/command.html#rc-file
# https://elv.sh/ref/runtime.html
# https://github.com/elves/elvish/issues/1726
if (not-eq $runtime:effective-rc-path $nil) {
	use './sources/login.elv'
	use './sources/interactive.elv'
}

There is no need for $true in that statement.

👍


Is command-exists a function you're defining?

Yes, providing the Dorothy configuration is loaded (which modifies the PATH).


It is unclear why you are loading rc.elv. That file would typically contain statements that don't make sense, and won't even work, in a non-interactive shell. For example, key bindings, command abbreviations, and REPL variable definitions. Here are just a couple of examples from my rc.elv:

Understood, will import Dorothy's configuration directly.

balupton added a commit to bevry/dorothy that referenced this issue Oct 8, 2023
balupton added a commit to bevry/dorothy that referenced this issue Oct 8, 2023
balupton added a commit to bevry/dorothy that referenced this issue Oct 8, 2023
balupton added a commit to bevry/dorothy that referenced this issue Oct 8, 2023
@krader1961
Copy link
Contributor

Using a absolute path also did not work.

For good or bad absolute import paths are not supported at this time.

Your most recent change to your project is still using eval and expecting the functions and variables it defines to be visible after the eval:

eval (cat '/home/runner/.local/share/dorothy/init.elv' | slurp)

That won't work without jumping through more hoops. In particular see the discussion about using the &on-end flag at https://elv.sh/ref/builtin.html#eval. But that is the hard way to do it for your situation. Just create an Elvish script that imports your module then references its content via its namespace. Export an (XDG_DATA_DIRS )[https://elv.sh/ref/command.html#module-search-directories] env var that includes your Elvish module directory.

@krader1961
Copy link
Contributor

@balupton Were you able to get your Elvish integration to work (in which case this should be closed) or do you need more assistance?

@balupton
Copy link
Author

The integration worked well enough. I'm leaving anything else up to any potential future dorothy and elvish user. Happy to close, thanks for all your assistance

@IngwiePhoenix
Copy link

Elvish does not need the equivalent because its rc.elv script is only loaded if the shell is interactive.

I wish this was documented in "Unique Semantics" or something. Had to dig my way here to figure this out.

That said, how does elvish handle user-applied paths? Say I have a cross-compile SDK in ~/sdks/lgtv to cross-compile software. This is a user-local path, so there wouldn't be a reason to add it into something like /etc/profile.d. How would I make elvish aware of that path when just doing something like elvish -c arm-webos-gcc ...?

@krader1961
Copy link
Contributor

I wish this was documented in "Unique Semantics" or something.

It's documented here.

That said, how does elvish handle user-applied paths?

It's not clear what you mean by "user-applied paths" or "user-local path". I'm guessing you mean customization of the PATH env var in the shell startup script(s). Not that POSIX shells like Bash also do not run ~/.bashrc, ~/.bash_profile, etc. (or whatever their equivalent of Elvish's rc.elv is) for non-interactive invocations. Elvish is no different from those shells. It is different from shells like Fish which do load their startup script regardless of whether or not the Fish process is interactive.

Elvish will inherit the PATH env var from the process that launches it. So the usual answer is to configure that process (or its parent process) to initialize PATH to suit your needs. You can also explicitly import an Elvish module that initializes PATH. For example, I have a ~/.config/elvish/lib/env.elv module where I initialize my preferred PATH value (among other things) that can be imported in an Elvish script with use env.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants