I have a bunch of xml file in the current directory.

Problem 1.

As far as I have read eval returns the results and stores it in a variable. But I get an error with the below command

find ./ -name '*.xml' | file=$(eval awk '{print $0}') ; echo $file

EDIT ( after ommitting eval as pointed by cas ) -

Omitting eval just returns an empty string.

find ./ -name '*.xml' | file=$(awk '{print $0}') ; echo $file

Problem 2.

I am just trying to learn bash and hence I made a complicated sequence of diffing the first two files from the output of find. The complicated sequence is just to understand the concepts of bash programming.

find ./ -name '*.xml' | file=$(awk '{print $0}') ; echo $file && diff -y $(sed '2q;d' $file) $(sed '1q;d' $file)
  • 3
    I don't know where to start. What is it that you're actually attempting to do, to begin with? – ilkkachu Jan 24 '18 at 19:15
  • You really don't want to use eval there. The command substitution ($(awk ...)) is what is storing the awk output in $file, not the eval. eval executes a string (whatever is on its command line) as sh code in a sub-shell, and is VERY easy to make disastrous mistakes with - I recommend forgetting it even exists until you know shell programming well enough to know the risks and how to avoid them. – cas Jan 24 '18 at 23:42
  • @ilkkachu diffing the first and the second file returned by find. – infoclogged Jan 25 '18 at 1:28
  • @cas - i just get an empty string on executing the first command ( without eval ). I was expecting the name of all the .xml files. – infoclogged Jan 25 '18 at 1:44
  • 1
    even if it did do something like you expect, it wouldn't set $file anyway because pipes are executed in a sub-shell, and sub-shells can NOT affect the environment (including variables) of their parent. this comes up quite frequently on this site, there's a good explanation here: unix.stackexchange.com/a/9994/7696 – cas Jan 25 '18 at 1:52

Here's a very brief example of one way to do it using an array in bash:


IFS=$'\n' files=( $(find . -name '*.xml' | head -n 2) )

if [[ -n ${files[0]} && -n ${files[1]} ]] ; then
  diff "${files[0]}" "${files[1]}"

This runs the find ... | head -n 2,command, stores each line (the returned filenames) into array $files. If the first two array elements (0 & 1) are non-empty, then it runs diff with those filenames.

Note that this will break if any of the directory or filenames contain newline characters.

  • thank you. Especially your comments / answers clears the concept of IFS, <(..), arrays in bash etc. I guess, using head -2 is not required. Also, the command runs if I remove IFS=$'\n'. Do you know why? And why do you use a space in between ( and $? The command runs also without a space. Well to sum it up, using array is one technique of getting the file list. Is it also possible to use find and sed to do the same thing? It would be intresting to to know why my initial attempt with find and sed doesnt work. – infoclogged Jan 25 '18 at 11:39
  • The IFS=$'\n' sets newline as the input delimiter - i.e. it stops the shell from word-splitting find's output on spaces or tabs. To see difference, add typeset -p files before the if statement and then touch "foo bar.xml" to create a .xml filename with a space in it. then run the script: without setting IFS, $files wouldl contain foo (no extension) and bar.xml instead of foo bar.xml. – cas Jan 25 '18 at 11:51
  • I add spaces to improve readability, in this case to highlight the fact that the script is populating an array with that command substitution line. you could use sed instead of head, but why bother? sed -n 1,2p doesn't do anything that head -n2 doesn't. Your initial attempt didn't work because pipes run in sub-shells, and sub-shells can not change the parent shell's environment....you might have set $file in the sub-shell, but that wouldn't affect the parent's $file variable. – cas Jan 25 '18 at 11:55

You can use:

diff "$(find . -type f -name 'diff')" "$(find . -type f -name 'diff2')"

This way you can search for the diff file, search for diff2 file and compare them with the diff tool.

  • @cas - <(...) wont diff the files. Instead it diffs the output of the two finds. – infoclogged Jan 25 '18 at 2:06
  • @cas - exactly that is why, I wanted to pick the 1st and the 2nd file name from the list of filenames using sed ( 1q;d or 2q;d ) . See my question above. If you have an answer for that - then I will mark it as an answer. – infoclogged Jan 25 '18 at 8:45

To expand the accepted answer, for an overall understanding, I am posting my own answer.

Solution to Problem 1.

The first problem is use of eval. eval is unnecessary here. Its enough to do a command substitution using $(...)

find ./ -name '*.xml' | files=$(awk '{print $0}') ; echo $files

The second problem is the pipe. As pointed out by @cas - pipe runs in their own subshell. Hence the variable file is empty. In order to solve this, remove awk and store the result of find directly in the variable.

files=$(find ./ -name '*.xml') ; echo "${files}" 

But, the above command is not an array. In order to make it an array, use a parantheses.

files=($(find ./ -name '*.xml')) ; echo "${files[0]}" 

should be done. Now, the use of double quotes in the variable ${files} is a good practise, because otherwise, spaces in file names will create problems. Apart from that the next good practise would be to use IFS . This makes sure, that the shell wont word-split on spaces or tabs. IFS=$'\n' would word-split on new lines ( output of find ) before storing the content in the variable $files.

IFS=$'\n' files=($(find ./ -name '*.xml')) ; echo "${files[0]}" 

So, the final command to diff the two files or any two files is

files=($(find . -name '*.sh')) ; echo "${files[0]}" ; diff -y "${files[0]}" "${files[1]}"

Here 0 and 1 can be replaced by any of a valid number in the array ( $files ).

Not the answer you're looking for? Browse other questions tagged or ask your own question.