0

I'm writing this simple script to cd to multiple folders from a file, but the cd doesn't work.

I have tried solutions in Why doesn't "cd" work in a bash shell script? and Shell script change directory with variable

But all didn't work.

Here is my script:

  cat fileName.txt | while read line; do
        echo $line
        line=$(echo $line | tr -d '\r')
        cd "$line"
    done




+ inputFile=drugs800Folders.txt
+ IFS=
+ read -r line
+ line=CID000000923
+ cd CID000000923
+ awk '$11 < 0.05 {print $1 $2 $3 $8 $10 $12}' groups.txt
+ IFS=
+ read -r line
+ line=CID000001003
+ cd CID000001003
FilterFuncOutput.sh: line 5: cd: CID000001003: No such file or directory
+ exit

@Charles Duffy so it works, and did the awk for the first folder in the list, but not for remaining folders after the first line Does the first line only selects the first file in the list ?

Community
  • 1
  • 1
JinGao
  • 3
  • 4
  • How do you run the script? – choroba Feb 05 '16 at 20:52
  • do you see the result of echo? – guhur Feb 05 '16 at 20:52
  • 1
    I think its imposible to change the pwd from a script, because it runs in a subshell with its own "PWD". Perhaps you could write that loop inside a function, in the file .bashrc, in order to you can call it from your actual bash session, instead of calling a external script. – ABu Feb 05 '16 at 20:54
  • I guess it works. Try `pwd` after `cd $line`. – Diego Torres Milano Feb 05 '16 at 20:54
  • How do you know it doesn't work? What behavior do you see? Note that a `cd` inside a script only affects that script; once it terminates and returns to the shell, it has no effect. – Keith Thompson Feb 05 '16 at 20:59
  • This is a variant of BashFAQ #24: http://mywiki.wooledge.org/BashFAQ/024 – Charles Duffy Feb 05 '16 at 21:23
  • 1
    @KeithThompson, ...in this case, it doesn't effect any line after the subshell the `while` loop's pipeline component is in exits either. – Charles Duffy Feb 05 '16 at 21:23
  • 1
    Also, `echo $line` is bad juju -- use `printf '%q\n' "$line"` if you want to display contents in a way that makes hidden characters, etc. explicit and doesn't have other undesired side effects (expanding globs, consolidating runs of whitespace, etc). – Charles Duffy Feb 05 '16 at 21:25
  • ...or, better, just use `set -x` to debug your scripts, and don't mess around with `echo` to print at all. – Charles Duffy Feb 05 '16 at 21:27
  • @Peregring-lk, does the OP really want to change the **calling** shell's working directory? I don't find anything in the question that makes this clear in either direction. – Charles Duffy Feb 05 '16 at 22:33

3 Answers3

2

Unless you have the lastpipe shell option set (and the prerequisites for its operation are met), all elements of a shell pipeline in bash run in their own subshell. Consequently, side effects of those pipeline components on the shell's state are thrown away when that subshell exits.

Furthermore, edits and comments have made it clear that you don't want each cd to take place after the other (thus, with the net effect being equivalent to cd line1dir/line2dir/line3dir/line4dir), but all to be relative to the starting directory. In the future, make constraints of this kind clear in your initial question.

Fortunately, you don't need a pipeline here at all:

#!/bin/bash
inputFile=${1:-fileName.txt}
owd=$PWD             # store original directory
while IFS= read -r line; do
  line=${line%$'\r'} # remove a trailing CR if any exists
  cd "$owd/$line" || continue
  echo "Now in directory $PWD"
done <"$inputFile"

Now, let's look at the specific things this does differently:

  • The cat call (and thus the pipeline from its output, which resulted in the cd having no continued effect after the loop exited) is gone.
  • Clearing IFS allows names ending in whitespace to be passed through unmodified.
  • Using the -r argument to read prevents names with literal backslashes from being munged.
  • Using || exit on the cd line ensures that your script won't continue to operate from the wrong directory in the event of a failure.
  • The echo $line is simply gone. It's not an accurate debugging measure to begin with; if you want to see what your script is doing, run it with bash -x yourscript or add a set -x line.

BashFAQ #24 goes into detail with respect to your root cause here.

Charles Duffy
  • 280,126
  • 43
  • 390
  • 441
  • You might also want to verify that `$line` is non-empty. `cd ""` does nothing and doesn't report an error. – Keith Thompson Feb 05 '16 at 21:38
  • Just a reminder, `$PWD` is not always the same as `$(pwd)`, since the variable is not readonly. Try this on for size: `PWD="Hello there"; echo "$PWD"`. While it's not a problem in this example, it's important to keep in mind that external factors might change `$PWD`, and that relying on it could have negative consequences. – ghoti Feb 05 '16 at 21:45
  • @ghoti, sure, but it's black-letter POSIX that any application that modifies `PWD` is breaking its environment. ("If an application sets or unsets the value of PWD , the behaviors of the `cd` and `pwd` utilities are unspecified", from http://pubs.opengroup.org/onlinepubs/009695399/utilities/xcu_chap02.html). – Charles Duffy Feb 05 '16 at 21:50
  • @ghoti, ...thus, modifying the value of the `PWD` variable is allowed to break the `pwd` utility, so using `$(pwd)` isn't guaranteed to be safe against such tampering. – Charles Duffy Feb 05 '16 at 21:50
  • @KeithThompson, is it a bug for the loop iteration associated with an empty line of input to do nothing and not a report an error (as it would do without any checks)? If not, I'd argue that no modifications are needed. :) – Charles Duffy Feb 05 '16 at 21:52
  • It depends on the intended behavior. But that particular misfeature is a potential problem for something like `cd "$var" || exit 1 ; rm -rf foo` – Keith Thompson Feb 05 '16 at 22:00
  • @Charles Duffy Thanks a lot for your detailed answer. The script works only (cd to the first name from the input file) and then can't do the same for the rest of the folder names in the input file. The input file contains folder names per line. I appreciate your response. – JinGao Feb 06 '16 at 13:39
  • @JinGao, if you run your script with `bash -x yourscript`, what does that show taking place? – Charles Duffy Feb 06 '16 at 18:47
  • @Charles Duffy I added my script in the original post! Thanks, – JinGao Feb 07 '16 at 08:49
  • well, that makes it very clear what's happening: You don't have a directory named `CID000000923/CID000001003`, but because you're trying to run one `cd` after another, that's where it's trying to go. – Charles Duffy Feb 07 '16 at 15:57
  • @JinGao, ...the way you asked your question, everyone trying to answer it assumed that an equivalent to `cd dir1/dir2/dir3/dir4` was what you actually wanted. In the future, be mindful as to communicating intent. – Charles Duffy Feb 07 '16 at 16:01
1

this worked for me

cat fileName.txt | while read line; do
 echo "$line" 
 line="$(echo "$line" | tr -d '\r')"
 echo "$line"
 cd "$line" 
 echo "$PWD"
done

Also, just a shell programming tip. This is a great website for testing shell scripts syntax and common mistakes and has saved my life many times!!! http://www.shellcheck.net/

Justin A
  • 364
  • 4
  • 14
  • 1
    Sure, but that's a very temporary effect -- move your `echo $(pwd)` -- which is just a slower and buggier way of writing `pwd` or `echo "$PWD"` out of the loop and it has no effect again. – Charles Duffy Feb 05 '16 at 21:31
  • ...re: "buggier", see what happens if you run `echo $(pwd)` when you're in a directory created with `mkdir 'hello * world'`. – Charles Duffy Feb 05 '16 at 21:32
  • thanks for the clarification on $(pwd), I changed my answer to "$PWD" – Justin A Feb 05 '16 at 22:00
0

Useless use of cat creating a sub-shell?

while read line; do
    echo $line
    line=$(echo $line | tr -d '\r')
    cd "$line"
done < fileName.txt

might work.

Jens
  • 69,818
  • 15
  • 125
  • 179
  • I'd suggest `line=${line%$'\r'}` to have a less buggy removal of trailing CRs -- the current one is going to have a lot of undesired side effects. – Charles Duffy Feb 05 '16 at 21:22