-1

say, the file 'listall' contains the directory list and I want to list up all the files in those directories including the directory path. I wrote a bash script below.

#!/bin/bash
\rm listZ
echo ' ' > listZ
for i in `cat listall`; ls $i | sed -e '1,$s/^/$i\//g' >> listZ; done

But in the sed command, the $i is not replaced with the directory name.
I tried using double quotes for sed command but in no avail. This is in cygwin. How can I do it?

Chan Kim
  • 5,177
  • 12
  • 57
  • 112
  • use double quotes instead of single quotes in sed. – Avinash Raj Apr 02 '15 at 06:34
  • As I said, I tried that, it doesn't work in this case. (probably because, as paxdiablo said below, my variable $i contains some '/' characters. – Chan Kim Apr 02 '15 at 07:34
  • possible duplicate of [shell variables in sed script](http://stackoverflow.com/questions/7006910/shell-variables-in-sed-script) – NeronLeVelu Apr 02 '15 at 08:11
  • @NeronLeVelu My case doens' work with double quotes. – Chan Kim Apr 02 '15 at 08:16
  • problem is not double quote but content of $i, try `ls -1` but you still have to check for any meta character in name (any tha is a Regex meaning like `+[(.*&` – NeronLeVelu Apr 02 '15 at 09:37
  • yes, it has '/' and space in the directory names. '/' is covered by using ';' in sed command, and space is cover by setting IFS value. I do SAVEIFS=$IFS, IFS=$(echo -en "\n\b"), for i in .. do .. done, IFS=$SAVEIFS. It works ok now. – Chan Kim Apr 02 '15 at 10:53

3 Answers3

1

The single-quoted string is immune to variable substitution. However, it's a simple matter to move the variable to outside the single-quoted string, such as with:

pax> one=1 ; echo '
...> 1
...> 101
...> 3.14159' | sed 's/'$one'/x/g'

x
x0x
3.x4x59

In your case, it would be:

for i in `cat listall`; do ls $i | sed -e '1,$s/^/'$i'\//g' >> listZ; done
#                                                 ^^^^

Keep in mind that, as per one of your comments, if your file contains characters special to sed, they'll cause you grief. For example, if they contain / characters, you'll need to stop that from being interpreted by the sed s command.

Perhaps the easiest way to do this is to use a different separator that's not likely to be in the input file:

for i in `cat listall`; do ls $i | sed -e '1,$s?^?'$i'/?g' ; done
paxdiablo
  • 854,327
  • 234
  • 1,573
  • 1,953
  • I tried your example and it works. I was glad to see it and tried the same, but it doesn't work. Could you try it with the shell script? (for example, listall containing two lines '/usr/bin/' and '/usr/local/bin/' ? – Chan Kim Apr 02 '15 at 07:06
  • 1
    @Chan, that'll be because the `$i` variables contain `/` characters so will screw up the `sed` command. I'll provide an update. – paxdiablo Apr 02 '15 at 07:17
1

DontParseLs and DontReadLinesWithFor.

The problem you are asking about could be fixed by using double quotes instead of single quotes, but then, you need to escape any shell metacharacters which you do not intend for the shell to interpret (in other words, you'd have to backslash-escape the dollar sign in the address range 1,$, or use single quotes next to double quotes, and probably change to a different delimiter to cope with slashes in the value of the variable $i; '1,$'"s:^:$i:").

But your entire task can be performed as a single command.

IFS=$'\n' find $(cat listall) -mindepth 1 -maxdepth 2 -print >listZ

The IFS is only strictly necessary if listall contains file names with whitespace in them. You would still in principle need to escape any other shell metacharacters (asterisks, square brackets, question marks). A more robust workaround would be

while read -r directory; do
    printf '%s\n' "$directory"/* "$directory"/*/*
done <listall >listZ

(I misunderstood your question at first, and assumed listall contained file names, not directory names. I'm keeping my original answer below.)


sed "s:^:$i/:" listall >listZ

provided you don't actually need an empty space on the first line (maybe add '1i\ ' to the sed script then). The address range 1,$ is the default in sed, so specifying it is redundant. Because there can only be one substitution per line, the /g flag is also redundant (it means, substitute all occurrences on one line, instead of just the first). If there could be colons in $i, use a different delimiter.

tripleee
  • 175,061
  • 34
  • 275
  • 318
  • the listall file contains only the directory names like '/usr/bin' and /usr/local/bin' and doesn't contain the file names in those directories. – Chan Kim Apr 02 '15 at 07:32
  • your "find $(cat listall) -maxdepth 1 -print >listZ" method pretty much does the task (with minor additional edit - it prints the directory name too..). I'm waiting for the 'sed' method from paxdiablo just for curiosity because it won't need additional human edits. BTW, Combining ' ' and " " doesn't work for me. – Chan Kim Apr 02 '15 at 07:44
  • 1
    Added `-mindepth 1` to omit the directory name. But I recommend the `while` solution because it's more robust against wacky file names. – tripleee Apr 02 '15 at 07:50
  • I'm sorry, but the 'listall' contains direcotory names as you understand. But the 'files' under those directories are also directories having many files underneath. I want to listup the directories under those directories. Your new method is giving me all the leaf files.. – Chan Kim Apr 02 '15 at 07:56
  • Huh? No, it enumerates the directory entries in the directory itself; directory names and file names. If you want to go one directory deeper, change the `-maxdepth` to 2 (or omit it completely to recurse as deep as possible). – tripleee Apr 02 '15 at 08:02
  • Updated answer to do this instead. This will still list the directory names as well as the files in those directories. If you really want the behavior of `ls` then maybe this is one of the few times where really you should use `ls` after all... – tripleee Apr 02 '15 at 08:05
  • This is the test result. The listZ file shows : /home/chankim/dira/dira1 /home/chankim/dira/dira1/filea1a /home/chankim/dira/dira1/filea1b /home/chankim/dira/dira2 /home/chankim/dirb/dirb1 /home/chankim/dirb/dirb1/fileb1a /home/chankim/dirb/dirb1/fileb1b /home/chankim/dirb/dirb2 /home/chankim/dirb/dirb2/fileb2a /home/chankim/dirb/dirb2/fileb2b – Chan Kim Apr 02 '15 at 08:12
0

As noted by paxdiablo(and thanks to tripleee), my problem was that the shell variable contained several '/' characters, which messed up the sed substitute command. So we can avoid the problem using another delimiter like ';' instead of '/' for sed. So the correct way is

#!/bin/bash
\rm listZ
echo ' ' > listZ
for i in `cat listall`; ls $i | sed 's;^;$i' >> listZ; done

Note, in my case the file listall looks like

/usr/bin/
/usr/local/bin/
Chan Kim
  • 5,177
  • 12
  • 57
  • 112