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

.bashrc equivalent? #51

Closed
rashil2000 opened this issue Jan 12, 2021 · 17 comments
Closed

.bashrc equivalent? #51

rashil2000 opened this issue Jan 12, 2021 · 17 comments
Labels
question Question about something

Comments

@rashil2000
Copy link
Contributor

Is there a way to initialize some variables, aliases (doskeys), much like .bashrc or .zshrc or PowerShell's profile in Clink? They should run/get initialized every time Clink is injected.

Thanks!

@chrisant996
Copy link
Owner

chrisant996 commented Jan 12, 2021

I'm not sure what you're getting at:

Are you trying to avoid writing Lua scripts? Are you asking for a second language to be added to Clink?

@rashil2000
Copy link
Contributor Author

I was looking for something like this: Currently I have a script in my home directory called init.cmd, which auto sets some aliases and variables at each shell startup. The location of this script has to be added to the Autorun variable of Command Processor in the Windows Registry. The script looks like this:

@echo OFF
set WTSettings="C:\Users\RashilGandhi\AppData\Local\Packages\Microsoft.WindowsTerminal_8wekyb3d8bbwe\LocalState\settings.json"
set ClinkReadlineHistory="C:\Users\RashilGandhi\AppData\Local\clink\clink_history"

doskey arch=wsl -d arch-linux genie -s
doskey admin=sudo
doskey ls=dir
'
'
'

and now this basically acts as a .bashrc for Windows CMD.

I was basically asking if there was a way to do this through Clink at each injection. I'm not aware if doskeys and variables (that can be accessed in the shell itself) can be set through Lua scripts, if yes, could you tell me how?

@chrisant996
Copy link
Owner

chrisant996 commented Jan 12, 2021

I think what you're asking about is really more of a shell feature, but Clink isn't a shell and isn't trying to be a shell. .bashrc is part of the Bash shell itself. CMD's equivalent is the AutoRun regkey.

Clink runs Lua scripts, and you can do via Lua all of the things that .bashrc can do, you'd just go about it a little differently. Clink uses Readline, and so it already has a .inputrc file whose format is defined by Readline and shouldn't be extended because it would break compatibility with existing .inputrc files, etc. You can currently share them between Bash and Clink, because inputrc files are only about Readline, not about the shell. But you're asking for new shell features in CMD, and that heads into murky territory. Try using Lua first.

There is a lot of Lua API documentation in clink.html, and a lot of public Lua documentation. What have you found so far from those?

Something to know about me: I don't tend to just hand out answers when the questions are easy to answer without my help. In those cases I try to ask questions to spark critical thought. "Give a man a fish, feed him for a day; teach a man to fish, feed him for life."

So, think about what it is you're trying to do, and what would be needed. Try searching for those things. If you've done that already, share what's been tried so far so that the respondent (me, in this case) doesn't have to guess at what's being done and try to predict what might have been missed. Try to make it inexpensive and easy for someone to respond. 🙂

One suggestion, though -- launching processes is expensive. Maybe instead of launching doskey once per alias, make use of its input file mechanism to load a bunch of aliases at once.

P.S. This is also a good exercise to help ensure Clink has what it needs; if you run into problems that can't be solved or that have messy solutions, then that may reveal some spots where Clink can be enhanced (e.g. adding an os.setalias() function, except I'm pretty sure that launching doskey from Lua will have the desired effect and if so then that would be preferable since it would use existing well-known things instead of inventing a new wheel).

@rashil2000
Copy link
Contributor Author

rashil2000 commented Jan 12, 2021

There is a lot of Lua API documentation in clink.html, and a lot of public Lua documentation. What have you found so far from those?

I found the function os.setenv there, which can be used to set the variables. For settings aliases, I'm still looking as there is no built-in function.

Update:
I initially knew about io.popen function of Lua, but I thought it opened a separate process and didn't affect the current shell. Similar for os.execute.
But after some testing, it seems both these do affect the current shell, so either one of these should be fine, I guess? And I think os.execute should be a little faster since it doesn't have to open a file handle. Am I correct?

@chrisant996
Copy link
Owner

chrisant996 commented Jan 12, 2021

It's about how Doskey works, rather than about how popen works (and execute, etc).

doskey isn't a CMD command, it's a separate program. It's a separate process, but it affects the CMD process, so we can deduce there is some mechanism they use to communicate. So launching a doskey.exe process via popen or execute will behave the same (I agree execute is a better fit here).

(The details of how they communicate is public knowledge, but the point is we don't need to know that. We only need to know whether they do, and we can deduce that from observing that doskey.exe is a program rather than a command.)

@rashil2000
Copy link
Contributor Author

If that's the case, I can safely assume that neither io.popen and os.execute would work in a scenario like this: Let's say I run a Java program that sets the JAVA_HOME variable for the current shell. If this program is run through either of the above two commands, then I won't be able to access JAVA_HOME, because it has been set in a different process. Right?

Thanks for the info though; I learnt a cool thing about doskey.exe today!

@chrisant996
Copy link
Owner

Correct!

@rashil2000
Copy link
Contributor Author

Thanks, I'll close this.

@chrisant996 chrisant996 added the question Question about something label Jun 8, 2021
@rashil2000
Copy link
Contributor Author

If I'm understanding it correctly, the new clink_start.cmd script feature does exactly what the original issue requested, right? (and since it runs in the current session it can modify environment variables etc., as opposed to io.popen and os.execute)

@rashil2000
Copy link
Contributor Author

rashil2000 commented Jun 22, 2021

I should mention that the reason I have come back to this 6 month old issue is because there are things that absolutely can't be done by Lua scripts (in the shell's context). I have a couple of examples to illustrate this:

  1. FNM is a popular NodeJS version manager. It generates commands depending on the shell at runtime (which means they can't be hardcoded in a Lua script). The commands include setting a few environment variables, a function, and a doskey, all of which should be injected in the current shell session. The homepage of FNM recommends adding a startup script to AutoRun registry key and putting this in that script:
FOR /f "tokens=*" %i IN ('fnm env --use-on-cd') DO CALL %i

As you can see, this won't be possible to do in a Lua script.

  1. Perhaps this is a very rare use case. A more common use case would be Visual Studio Developer Command Prompt. VS provides some scripts that take architectures as arguments and inject the appropriate environment variables/compiler and linker paths etc. in the current session. The scripts are fairly complex and a lot of effort would be needed to translate all of them to Lua (even if it's possible).

The solution to both of these was just prepending the startup script name in the AutoRun registry key as follows:

%USERPROFILE%\init.cmd & clink.bat inject --autorun --quiet

This method isn't very reliable; I have found many instances where CMD shell just freezes in the presence of certain commands in the init.cmd (regardless of whether the clink inject part is there in the key or not).

I just wanted to say thanks for the clink_start.cmd feature; it provides a much more cleaner UX, without those freezing issue and without needing to mess with registry manually (and is technically equivalent to a startup shell script much like bash's .bashrc, zsh's .zshrc or pwsh's profile.ps1 :))

@chrisant996
Copy link
Owner

If I'm understanding it correctly, the new clink_start.cmd script feature does exactly what the original issue requested, right? (and since it runs in the current session it can modify environment variables etc., as opposed to io.popen and os.execute)

More or less. I didn't originally explain the technical reasons why it's difficult/problematic to fulfill the request (I later explained them in #91). The only way Clink can "run" a command line in the current session is to give control back to CMD. So the only way Clink can "run a startup script" is to notice the first time CMD asks Clink to prompt the user, and instead don't prompt, return a hidden command as though the user had typed in the startup script/program/command name. When CMD eventually asks Clink to prompt for the next time, then Clink has to figure out whether the previous command it returned resulted in any output and reset the cursor position accordingly, then show a prompt. So what the user experiences as "the first prompt" is in reality the second prompt.

I got tired of needing to explicitly specify a startup script, and decided to try an experiment to see how well the above might work in practice.

I should mention that the reason I have come back to this 6 month old issue is because there are things that absolutely can't be done by Lua scripts (in the shell's context).

The issue wasn't about "why" it was desired (I expect the desire is nearly universal), the issue was about "how" to even accomplish it. Injection into a process with Detours has many limitations as far as what is possible to do.

%USERPROFILE%\init.cmd & clink.bat inject --autorun --quiet

This method isn't very reliable; I have found many instances where CMD shell just freezes in the presence of certain commands in the init.cmd (regardless of whether the clink inject part is there in the key or not).

Can you share an example of a script that causes CMD to freeze when it's included in the AutoRun regkey? The only reason I know of for that to happen is if the script waits on something (such as input another process, etc). If there are other things that can cause a freeze then it would be useful to know -- there might be other side effects that can affect Clink as well.

@rashil2000
Copy link
Contributor Author

The only way Clink can "run" a command line in the current session is to give control back to CMD... So what the user experiences as "the first prompt" is in reality the second prompt.

Yes, I understood that from the discussion in #91.

Can you share an example of a script that causes CMD to freeze when it's included in the AutoRun regkey?

So the way I can reproduce this is like this:

  • Run clink autorun uninstall to clear the registry key.
  • Manually set the key value to %USERPROFILE%\init.cmd
  • In init.cmd, I have put these lines:
@echo OFF
bfetch

where bfetch is a bat script I have in my path, the contents of which are here.

  • This freezes the CMD window.
  • Now if I reinstall clink autorun through clink autorun install -- --quiet, and put the contents of init.cmd into clink_start.cmd, the script runs fine and shows the output.
  • Even the FNM script (shown below) hangs CMD, whereas in clink_start it works fine.
@echo OFF
for /f "tokens=*" %%i in ('fnm env') do call %%i

@chrisant996
Copy link
Owner

What is fnm and what does fnm env normally do? Looks like it's a separate process, and that process is probably hanging, thus causing CMD to "freeze". Because CMD must wait for fnm to finish executing.

@chrisant996
Copy link
Owner

chrisant996 commented Jun 22, 2021

It's a bug in your script -- or rather, your script is causing an extremely expensive infinite loop and creating thousands of new cmd.exe processes as quickly as possible, when the script is invoked as part of launching cmd.exe. (Use TaskMgr or Process Explorer to see the processes.)

Your script launches cmd.exe...

So:

  1. You launch cmd.exe.
  2. Cmd runs the autorun command, which launches cmd.exe.
  3. The new cmd runs the autorun command, which launches cmd.exe.
  4. Infinite loop.

This type of syntax launches a new cmd.exe in order to run the command that the for command will process (the wmic os get ... part):

for /f "tokens=* usebackq" %%f in (`wmic os get Caption^,Version^,FreePhysicalMemory^,TotalVisibleMemorySize /format:list`) do (

Just like io.popen() and spawn() and system() and so on do.

The reason it doesn't go into an infinite loop when used in clink_start.cmd is purely because Clink refuses to inject itself into a non-interactive cmd.exe, and the cmd.exe spawned by the script are non-interactive.

@rashil2000
Copy link
Contributor Author

rashil2000 commented Jun 22, 2021

What is fnm and what does fnm env normally do?

fnm env just prints 4-5 shell commands to stdout, like these:

set FNM_ENVIRONMENT=some\temp\path
set FNM_CONTEXT=some\other\path\
set FNM_SESSION_ID=somerandomnumber
set FNM_HOME=some\persistent\path
doskey cd=somefunctiontochangeversionondirectorychange

These commands need to be run in order to set up the FNM environment. The paths here depend on session ID, which are different for each CMD session.

@rashil2000
Copy link
Contributor Author

The new cmd runs the autorun command, which launches cmd.exe.

Oohhhh, now it makes sense!!!

This means that we should think twice before putting arbitrary shell commands in AutoRun batch scripts 😅

The reason it doesn't go into an infinite loop when used in clink_start.cmd is purely because Clink refuses to inject itself into a non-interactive cmd.exe, and the cmd.exe spawned by the script are non-interactive.

How do you detect interactive sessions? (In bash there is an automatic variable $- which contains the characer i when the session is interactive). Is the %CMDCMDLINE% variable being used?

@chrisant996
Copy link
Owner

bool host_cmd::is_interactive() const

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

No branches or pull requests

2 participants