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

history command idea #60

Open
aryarm opened this issue May 13, 2018 · 10 comments
Open

history command idea #60

aryarm opened this issue May 13, 2018 · 10 comments

Comments

@aryarm
Copy link
Collaborator

aryarm commented May 13, 2018

We've had a new idea for a command that aliases a bunch of your prior commands together into a single command. It will probably use bash history. It will be written in c because c is so much easier.

@aryarm
Copy link
Collaborator Author

aryarm commented May 13, 2018

Resources

@aryarm
Copy link
Collaborator Author

aryarm commented May 19, 2018

Proposed Interface

We can use alias with a new -h option: alias -h [num_history_lines] alias_name
An argument to -h can be optionally provided to specify how many of the previous lines from history to use. If it isn't specified, we could load all of the lines from the beginning of the current session's history. We can use unalias to delete an already existing alias.

problems that might arise:
  • what if the alias_name includes an equal sign or other characters that aren't allowed? what if there are commands included after the equal sign? would we just ignore them?
  • what if the alias_name already exists? will we just replace it? but then what if one of the commands in history is the command that we're replacing? (how does regular alias deal with this? is this just undefined behavior?)
  • how will we merge history lines together into a single line for alias? we can't just put semicolons at the end of each command because sometimes that doesn't work. (ex: if the line ends with &)
  • also what does bash history do with multi-line commands (ie those separated by \)? will this cause any trouble for us?

Perhaps we might also want to add functionality for editing the alias using the user's default editor? This might be useful, especially since I can imagine some of these aliases becoming really long. In this case, would it be better to store these aliases as bash scripts so that they can be easily edited without having to re-parse the alias as a single line? Might this also allow for advanced functionality whereupon the user can specify their own custom arguments and options?

@GiselleSerate what are your thoughts on all of this?

@GiselleSerate
Copy link
Owner

GiselleSerate commented May 19, 2018

@aryam7 are you working on myaliases on your lunch break? xD

EDIT: I realized it's Saturday, never mind me

@GiselleSerate
Copy link
Owner

  • Maybe we should only read legal characters for the alias name, and then we will ignore any illegal characters and anything that follows.
  • I think if alias_name already exists, we would replace it, as that's what normal alias does. If one of the commands in history is one we're replacing, it would do a recursive thing, so that's really bad and it would get stuck in an infinite loop, so I think we should check for that before we start aliasing.
  • If we write this into a bash script, multiple lines wouldn't be an issue.
  • I briefly investigated multiline commands and it basically just smashes them into one line all together, eliminating the slash and the enter. So I think that's totally okay and we can treat them like any other command.

I think we can use altalias in .bash_alias because I've already got decent option handling in that (and it passes directly to alias if the option handling doesn't fly on my end . . . maybe I should pre-handle the error messages to warn if it's not an s, h, or p option.)

I think writing these as bash scripts would be most optimal, because then our aliases could just call the applicable script. Then we'd have to figure out where the scripts are stored globally . . . likely we would want to put something in a folder elsewhere (like bin or something) that wouldn't get moved or installed in a different place relative to root. Also then we don't have to worry about multiple lines. I don't know what you're suggesting about editing the alias using the default editor (honestly giving them the filepath might be the best thing, unless you want to have a certain command that will be aliased to look in the folder and open it using their default editor).

@aryarm
Copy link
Collaborator Author

aryarm commented May 19, 2018

Cool, I agree with all of the bullet points. But I think recursive aliasing probably falls under the category of undefined behavior, along with referencing variables/functions/files that aren't loaded into the global environment on startup and all of the other crap that the user could throw at us. The original alias command just treats this as undefined behavior too, I believe.

Yeah, I think using bash scripts would be pretty neat. That route would also really maximize the flexibility of this command (ie the user could pass arguments to their scripts and do all of the complicated stuff that a script has the power to do).

I also like the idea of creating aliases that call the scripts; we could use this to add the scripts to the global environment and it wouldn't require us messing with $PATH or anything. Also unaliasing the script would literally be as easy as just unaliasing the command that calls the script -- we wouldn't have to write any extra code, since we could just leverage the unalias code you've already written. Also, if users want to see which scripts they've created, they can just use the already existing alias -p infrastructure; we wouldn't have to write extra code for that either.

Yes, I was thinking we could store these bash scripts in a folder inside .myaliases and create another option (maybe -e for "edit") for alias that would allow us to automatically edit those scripts using the default editor. That way, the user doesn't have to know the path to the scripts (and also doesn't have to type it out).
One downside of this interface is that it kinda implies the user can edit non-script aliases, as well. Unless we want to write code that also edits regular aliases, we might want to require that both the -h and -e options are provided together. I guess we would just ignore the num_history_lines parameter if it's provided in this case? I'm not exactly sure how best to approach this.

Let me know what you think!

@GiselleSerate
Copy link
Owner

Cool, I'm always down for leaving it as undefined behavior. I guess it could fail pretty badly if people really wanted to give us bad input.

We probably want to delete the script itself when we unalias, because I don't want to be littering the system with garbage files.

I think editing should happen in post. I think it's reasonable to have a -h flag and have that only save the file and then have a -e flag that you can do on a subsequent run to edit the applicable file. At that point, you'd only need to provide the name of the alias, which I suppose would be the filename. The downside being what if the file provided does not exist . . . I don't know if we want to error out at that point or set the alias and give them a new file . . . I'd lean towards erroring out because if it's a typo, that's kinda hard to undo.

Do we have a path to the my_aliases folder? I can't remember but we must, to get .usr_aliases . . .

Also, I've been looking into stuff and probably it's better to use execve than system (maybe cause I skimmed and it said "security concern"). We could have scripts in files that execve calls, and I will use my handy dandy fork knowledge from 105 if necessary.

@GiselleSerate
Copy link
Owner

I found a utility that turns shell script into C code, but I'm not sure that's really what we want.

@aryarm
Copy link
Collaborator Author

aryarm commented May 20, 2018

Ok. sounds good!
so use of the -e flag to edit an alias that doesn't have a bash script is undefined behavior? or an error or something?
yeah, we should have a path to it. I think it's in the $ABSPATH variable?
Ok! I'm all for using execve. I'm also not sure I know enough to say whether that utility is what we need

@GiselleSerate
Copy link
Owner

GiselleSerate commented May 21, 2018

Yeah, we can probably error out, that shouldn't be too much of a problem. I think we should probably do it explicitly, because if we leave it as undefined behavior then what we'll get is a usage error.
The only problem with execve is that we have to pass a script . . . haven't figured out if we can necessarily call stuff like ls and so forth or if we need to delegate that to a script. (I need to figure out how to call normal scripts before figuring out system-type calls.)
EDIT: I used the code from this link with slight modifications (i.e. only initializing the 0th element of myArray) and it was able to call ls properly.

@GiselleSerate
Copy link
Owner

GiselleSerate commented May 21, 2018

About execve

Here's a summary of what I've figured out about execve. @aryam7, if you have questions about things, lmk, I know I definitely underexplained forking but it's something we went over in 105 so I can definitely explain better/more if things are unclear.

Calling a local script

int main(int argc, char *argv[], char **env)
{
    char *exePath = strcat(getenv("ABSPATH"), "/.testscript");
    char *myArray[] = {"dontcare", "arg1", "arg2", NULL}; // Pass in single argument, array is null terminated.
    execve(exePath, myArray, env); // On success, will not return to function.
}

This calls a bash script called .testscript in the myaliases directory. Takes in two arguments. Argument 0 is replaced by the program name.

Calling a builtin function

int main(int argc, char *argv[], char **env)
{
    char *exePath = "/bin/ls");
    char *myArray[] = {"dontcare", NULL}; // Pass in single argument, array is null terminated.
    execve(exePath, myArray, env); // On success, will not return to function.
}

Calls ls.

Note about forks

I didn't add the fork code above for brevity, but execve will not return control to your function (part of why main isn't returning in my examples). If you'd like to do multiple tasks with no dependencies, you can:

if(fork()==0)
{
    // Child thread; do a thing
}
else
{
    // Parent thread; do another thing
}

Either may be done first, since they're now on separate threads, so you don't have control over that.

So normally if you want the parent to wait for the child to be done, you can put a waitpid before the task you want to wait (takes the child pid, which gets spit out of the fork() function, so you'll have to save it). I'm really not sure if that will work in this case with the execve because it isn't actually returning to the function . . . but maybe once execve is finished with the script it'll register as finished. Will test.

Caveats for your own C files

You gotta chmod your executables or else bash scripts will not be able to call them. You'll be able to call them, but since no one else has permission to execute, you'll probably have to . . . pass chmod 711? That would give you all privileges but leave only execute for everyone else. (I don't know if a script counts as a group member, or another user, or what . . . maybe something to investigate.)

Also, you have to run make or the executables won't exist in the first place. I've written the Makefile such that running make all should be sufficient, and once we figure out the proper chmod settings I can have it chmod the executable as soon as it's made as well, but that requires that the user run make in the directory. Which maybe my setup script can do eventually.

In conclusion

We don't want to use C files for everything, because they seem to be bad at handling sequential bash commands, but using them as a framework for calling bash scripts that execute certain commands might not be the worst thing in the world.
If we are only calling these certain bash scripts from C, we can pass in parameters by index rather than option handling, if that seems easier.
I'm not sure about overhead, but none of this seems to be working too slowly. Maybe we should watch the bloat; we're currently using a function to call a C script that calls a bash script. So unless we have a good reason, we might want to stick to bash? This seems an unsatisfying and duct-tapey answer, so maybe I'll keep looking. I think the problem is that our stuff links directly back into the system, and we mostly know how to do that with bash commands, so everything gets filtered through an extremely small bottleneck of execve. Maybe there's a better way to do this.

For history specifically

We can read straight from $HISTFILE with C, that's not a problem. Possibly a pain to read "last X lines from file" but that's okay. Calling still has to happen through bash, though. Unless we add $ABSPATH to $PATH? (Note: PATH=$PATH:$ABSPATH, but will append even if $ABSPATH already is in $PATH.) Or we . . . put our executables in bin. (I don't know if I want to do that.)

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

2 participants