Why won't my script work using the read and cd command?

About writing shell scripts and making the most of your shell
Forum rules
Before you post please read how to get help
Post Reply
magnetor1000
Level 2
Level 2
Posts: 74
Joined: Fri Apr 01, 2022 7:53 pm

Why won't my script work using the read and cd command?

Post by magnetor1000 »

This is my script:

Code: Select all

#! /bin/bash 

read -p "Which folder do you want to navigate to?: " folder

cd "$folder" 
It either does nothing (usually if I put in the / folder or one of its immediate children). Or it will say: ~: No such file or directory if I put in ~ for example. Same thing would happen if I put in $HOME. Nothing works.

Why isn't this script working?
User avatar
xenopeek
Level 25
Level 25
Posts: 27575
Joined: Wed Jul 06, 2011 3:58 am
Location: The Netherlands

Re: Why won't my script work using the read and cd command?

Post by xenopeek »

For the second part because the read command doesn't evaluate environment variables and shell builtins you type on the prompt. So when you type $HOME or ~ the folder variable gets those as literal values. If your run the commands on the terminal directly you can see that:

Code: Select all

$ read -p "Which folder do you want to navigate to?: " folder
Which folder do you want to navigate to?: $HOME
$ cd "$folder"
bash: cd: $HOME: No such file or directory
If you want to to evaluate the variable after the fact you could use eval:

Code: Select all

$ eval cd "$folder"
# pwd is now $HOME
Using eval however is a cause of bugs and a major headache to do right (sanitizing the user input so it is safe to be eval'd).

As a trivial example:

Code: Select all

$ read -p "Which folder do you want to navigate to?: " folder
Which folder do you want to navigate to?: $HOME; echo "deleting your files"
$ eval cd "$folder"
deleting your files
That cd's to $HOME but it also runs the second command that I typed on the prompt!

The safe way would be to evaluate certain special cases yourself. For example if $folder starts with ~, do a string replace yourself to replace that ~ with the user's home directory. Same if it starts with $HOME.
Image
magnetor1000
Level 2
Level 2
Posts: 74
Joined: Fri Apr 01, 2022 7:53 pm

Re: Why won't my script work using the read and cd command?

Post by magnetor1000 »

I am able to replicate that behavior on the terminal prompt itself (eval cd "$folder" works), but it does not work within a bash script when I run it in the same terminal. And just to confirm that the script ran correct echo $? does indeed return 0. Why would this work live in terminal but not in a script in the same terminal?
User avatar
xenopeek
Level 25
Level 25
Posts: 27575
Joined: Wed Jul 06, 2011 3:58 am
Location: The Netherlands

Re: Why won't my script work using the read and cd command?

Post by xenopeek »

It works fine. Put a echo $PWD on the line after the eval cd "$folder" and you'll see the current directory for the script is changed. It doesn't change the directory for you, if that's what you were expecting. When you start the script a new process is created in its own environment. Changes the script does to the directory affect the script's environment, not yours.

If you want the script to affect your environment you should not run it as a program, but source it. E.g. if you have a script like this:

Code: Select all

#!/bin/bash
cd /etc
Running it as ./scriptnamedoes nothing to your environment, but it changed the directory for the script for any commands after that cd line. If you source it with . scriptname or source scriptname it changes your terminal to /etc.
Image
magnetor1000
Level 2
Level 2
Posts: 74
Joined: Fri Apr 01, 2022 7:53 pm

Re: Why won't my script work using the read and cd command?

Post by magnetor1000 »

xenopeek wrote:
Mon Sep 26, 2022 3:52 pm
It works fine. Put a echo $PWD on the line after the eval cd "$folder" and you'll see the current directory for the script is changed. It doesn't change the directory for you, if that's what you were expecting. When you start the script a new process is created in its own environment. Changes the script does to the directory affect the script's environment, not yours.

If you want the script to affect your environment you should not run it as a program, but source it. E.g. if you have a script like this:

Code: Select all

#!/bin/bash
cd /etc
Running it as ./scriptnamedoes nothing to your environment, but it changed the directory for the script for any commands after that cd line. If you source it with . scriptname or source scriptname it changes your terminal to /etc.
Okay, so I implemented that in my script so I can enable the script to change my environment. I put the first line as: source "$0" (I also tried source scriptname).

And both resulted in the following message and both exited with an exit code of 139:
Segmentation fault (core dumped)

What you suggested only works within bash and outside the program. Is there no way to have a program be able to change my environment without needing to source it in bash itself, but rather have the possibility to implement this within the script?
magnetor1000
Level 2
Level 2
Posts: 74
Joined: Fri Apr 01, 2022 7:53 pm

Re: Why won't my script work using the read and cd command?

Post by magnetor1000 »

Also are there other commands (other than the read command) that do not evaluate environment variables and shell builtins typed in the prompt?
User avatar
xenopeek
Level 25
Level 25
Posts: 27575
Joined: Wed Jul 06, 2011 3:58 am
Location: The Netherlands

Re: Why won't my script work using the read and cd command?

Post by xenopeek »

magnetor1000 wrote:
Wed Sep 28, 2022 7:53 am
Okay, so I implemented that in my script so I can enable the script to change my environment. I put the first line as: source "$0" (I also tried source scriptname).

And both resulted in the following message and both exited with an exit code of 139:
Segmentation fault (core dumped)
I did not suggest that :o That causes an endless loop.

You can only use source within a script, to include another script in its environment, or on the terminal to include it in your environment. As you're trying to use it doesn't make sense: you are still executing the script as a program so it will not affect your environment. Only when you source it yourself on the terminal (don't put source command in the script itself) will it work.
magnetor1000 wrote:
Wed Sep 28, 2022 7:53 am
Is there no way to have a program be able to change my environment without needing to source it in bash itself, but rather have the possibility to implement this within the script?
You could put the code in a shell function instead of in a script. The shell function code in your .bashrc or a file sourced from there. Or you could exec a new shell session from the script, that you set up to cd to the directory (after which your script ends because exec replaces its execution with the new shell session). But same as with sourcing your script, these ways only work if you're invoking them from the terminal.

What are you actually trying to do with this? I think we may be heading in to XY problem territory.
Image
magnetor1000
Level 2
Level 2
Posts: 74
Joined: Fri Apr 01, 2022 7:53 pm

Re: Why won't my script work using the read and cd command?

Post by magnetor1000 »

xenopeek wrote:
Wed Sep 28, 2022 8:41 am
magnetor1000 wrote:
Wed Sep 28, 2022 7:53 am
Okay, so I implemented that in my script so I can enable the script to change my environment. I put the first line as: source "$0" (I also tried source scriptname).

And both resulted in the following message and both exited with an exit code of 139:
Segmentation fault (core dumped)
I did not suggest that :o That causes an endless loop.
Oh yes I know you did not suggest I do that. I was just trying to implement your solution within the script. I know you also said that you advise against using eval because of potential issues, but at the moment I do not have any other solutions for what I want to do.
xenopeek wrote:
Wed Sep 28, 2022 8:41 am
You can only use source within a script, to include another script in your environment, or on the terminal to include it in your environment. As you're trying to use it doesn't make sense: you are still executing the script as a program so it will not affect your environment. Only when you source it yourself on the terminal (don't put source command in the script itself) will it work.
magnetor1000 wrote:
Wed Sep 28, 2022 7:53 am
Is there no way to have a program be able to change my environment without needing to source it in bash itself, but rather have the possibility to implement this within the script?
You could put the code in a shell function instead of in a script. The shell function code in your .bashrc or a file sourced from there. Or you could exec a new shell session from the script, that you set up to cd to the directory (after which your script ends because exec replaces its execution with the new shell session). But same as with sourcing your script, these ways only work if you're invoking them from the terminal.

What are you actually trying to do with this? I think we may be heading in to XY problem territory.
Haha, it's very possible we are in XY Problem territory.

Okay, here's the broader picture:

I am doing a bash course in Udemy called "Bash Mastery", and I am up to a big assignment that essentially requires everything I learnt up until this point put together (loops, arrays, special parameters, user input, select command, read command, case statements etc etc).

There are 3 script assignments contained within my big project and I have to combine scripts 1 and 2 as options to select from for the 3rd script (the 3rd script looks to be the easiest to create, as it seems to be just a select menu with each case just calling upon either script 1 or 2 to run).

See attachment for full assignment details:
Script+Building+Assignment+Brief-1.pdf
(79.48 KiB) Downloaded 11 times

I am currently working on Script 1: cruft_remover.sh. I am fairly confident that I can fully complete that first script and it could work perfectly if the end user (me in the case) would put the literal full extended pathname of the directory to be stored as the variable for the read command and for the rest of the script to execute commands based on that stored variable.

However, as a matter of personal preference I'd rather just be able to use the characters ~/path or . or .. so I do not have to type out the entire full pathname every time because that is a pain the ass.

And being that this script is going to DELETE files on my system, I want to make sure first that I can with 100% certainty know how it works within the way I am used to working with filepaths in terminal.

So what I did was create a different script, testing the read command with something inconsequential such as cd, echo, ls so no risk is posed to anything on my system while I try to understand the way this works deeply.

That is the reason I submitted my original post here in the way I did, despite the fact that it is not directly what the assignment asks of me.

Does that make sense? And in general am I going about this the right way? I am really trying to get to a point where I understand the process deeply because I really want to avoid just being a "script kiddie" of sorts.

I guess I could still write out the full pathname and my entire post here would irrelevant, but that's just really cumbersome, and I'd rather solve this issue now with a simple script rather than later with a much more complex script that will be much harder to deal with and/or fix. That is the reason I want to get this to work in the way I am trying, despite all these apparent obstacles.
User avatar
xenopeek
Level 25
Level 25
Posts: 27575
Joined: Wed Jul 06, 2011 3:58 am
Location: The Netherlands

Re: Why won't my script work using the read and cd command?

Post by xenopeek »

As I said, you can replace ~ with /home/username manually without using eval. If you just want to replace '~' with /home/username if the user types '~' or '~/something', that can be done easy. Something like:

Code: Select all

[[ $folder == '~' || $folder == '~/'* ]] && folder="${folder/#\~/$HOME}"
That's using parameter expansion: https://www.gnu.org/software/bash/manua ... nsion.html

'.' and '..' don't need to be expanded so nothing special needed for those.

Tilde expansion as Bash calls it can do quite a bit more than replace '~' with /home/username: https://www.gnu.org/software/bash/manua ... nsion.html. There's an example here https://stackoverflow.com/a/18742860 how to implement that natively in a Bash function but that's a whole lot more complex.

For testing what the script is doing, put some echo statements in between :) Like after a cd command do the echo "we're now in: $PWD" or just add a pwd command and that will print the working directory for the script.
Image
Post Reply

Return to “Scripts & Bash”