3

I have 3 files: a.txt, b.txt and c.txt. I want to create 3 files: a.test, b.test and c.test containing the simple text abracadabra.

Basicaly, the point is to create the .test files for each .txt file, and fill them with some text. I am trying to use xargs to do this.

anlx2626> ls
a.txt  b.txt  c.txt

anlx2626> ls *.txt | awk -F'.txt' '{print $1}' | xargs -I {} echo "abracadabra" > {}.test

anlx2626> ls *.test
ls: No match.

Could someone point out why using the {} placeholder for line-by-line pipe doesn't work the way I expect it to?

4 Answers 4

2

In this code:

xargs -I {} echo "abracadabra" > {}.test

xargs will not see the redirection operator > at all: it will be interpreted by your shell instead, creating a file named, literally, {}.test.

One way to do what you ask using xargs is this, which lets sh interpret the redirection operator:

xargs sh -c 'for i do echo abracadrabra > "$i"; done' sh

But you would probably be better off not using xargs at all, which other answers explain how to do.

6
  • What if there's a $(rm -rf "$HOME").txt file in the current directory? Commented Dec 2, 2015 at 17:03
  • Then Bad Things would happen. I added a note to that effect.
    – dhag
    Commented Dec 2, 2015 at 18:32
  • 1
    Then make it xargs sh -c 'for i do echo abracadrabra > "$i"; done' sh Commented Dec 2, 2015 at 19:46
  • I tried to answer the explicit question, that asks why the redirect doesn't seem to work. I fully agree that your answer is a much better approach to actually creating .test files. I'm not sure what your point is: do you mean it would be better if I omitted my attempt at fitting a solution within xargs' constraints?
    – dhag
    Commented Dec 2, 2015 at 20:33
  • 1
    I mean that you shoud not embed the {} in the shell code, pass it as an argument to sh. You could also make it xargs -I{} sh -c 'echo abracadabra > "$1"' sh {} which wouldn't have tha code injection vulnerability but would run one sh per file. Commented Dec 2, 2015 at 20:40
2

awk can output text, not need for echo or xargs. Your approach has issues for file names with more than one dot, like foo-1.2.txt.

It would also have issues for filenames with newlines in them or if there are directories whose name ends in .txt.

For once, I'd use a loop here, for instance with zsh:

(set -o noclobber; for f (*.txt(N)) echo abracadabra > $f:r.test)

Or POSIXly:

(set -o noclobber
 for f in *.txt; do
   echo abracadabra > "${f%.*}.test"
 done)

The noclobber is to avoid overriding an already existing file. Note that it omits hidden files. If there's no txt file, the POSIX sh variant will create a *.test file.

0

Although this has been answered adequately, I'm just adding this to show another way:

while IFS= read -r
do
echo abracadabra > "${REPLY}.test"
done < <(find . -maxdepth 1 -name "*.txt" -exec basename -s .txt {} \;)

Here find is used to list the text files. maxdepth is used to stop find from recursing into child folders. basename -s .txt is being used to remove the txt extension right at the start because except to list the names we don't need it at all. In the while loop, we simply use the name without the extension, and use string substitution to add the .test extension, while echoing to the file. Note that we have used the default variable of while, which is $REPLY.

3
  • 2
    Welcome to the site, and thank you for your contribution. Please note however that this will not work if the filenames contain newlines (which is rare, but permitted). See here for a discussion on this.
    – AdminBee
    Commented Sep 23, 2021 at 14:54
  • 1
    It also runs one basename command (with the non standard -s option) for each file which is rather inefficient and assumes a Korn-like shell for read defaulting to read REPLY and process substitution Commented Sep 23, 2021 at 15:25
  • I was under the impression that IFS= implies null character as ending and so newlines would be taken care of. Is it that a -print0 needs to be explicitly stated? Also, I see the point that running basename every time may be inefficient. But this does work for me in git bash.
    – Rajib
    Commented Sep 23, 2021 at 16:04
-2
for l in $(ls *.txt); do echo "abrakadabra" > $l; done
3
  • 5
    Hello nepsse. Why are you parsing ls instead of just using for l in *.txt? What's $1 intended to be? Generally here we prefer code to be explained rather than just dumped into an answer. Commented Oct 22, 2018 at 18:41
  • 3
    It also doesn't address the question in the Question.
    – Jeff Schaller
    Commented Oct 22, 2018 at 19:09
  • 2
    This overwrites the original txt files Commented Oct 22, 2018 at 22:47

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .