Any simple way to do for loop pipe , such as ` ls | grep ".*.7z" | 7z -e {}`
I am curious that is there an function/command to cope for loop pipe ?
It is easy to occur below demand
- you run a command return multiple line
- you need process each line
- pass each line to another function/command
For example:
you need find some path match a parttern , move each to another place :
# 1.
find path_A -name '*AAA*' -exec mv -t path_B {} +
# 2.
find path_A -name "*AAA*" -print0 | xargs -0 -I {} mv {} path_B People would be confused,
- why need mv
-t(I mean, through a [for loop pipe] you can usemv {} some_path,just like what you wrote without pipe ) - why exec or | xargs is write in that way , xargs need much more options, but they are actually no need in many computer language 。
- why I can't use
ls ... mv -t path_B {} +, orls | xargs -0 -I {} mv {} path_B, in other words , why it doesn't work after I change to another command ? - Why I failed when change
mvto7z:find path_A -name '*AAA*' -exec 7z -t {} +?
NOTE: Above are not question, just examples!!
I asked this question for a common and simple way to cope for loop pipe . Is there a simple and common way to do:
# 0
command with option (usually you use a command like this)
# 1
command with option [for loop pipe] command with option | command with option
# 2
command with option [for loop pipe] command with option | command with option [for loop pipe] command with option
# 3
....So you don't need change command with option when you use [for loop pipe] . This would be much convenient for daily use .
Update
I put all the example just want to say normal way is confusing when different comand has it own style to cope with multiline input, it would need exec or xargs or some else options . if we have a for pipe command which can connect two command with option . It would be much easy to use
I mean, if you are using an advanced language. For example python,
def pipe(x, func, *func_options): return func(x, *func_options)
def forloop_pipe(x, func, *func_options): return [func(i, *func_options) for i in x.split('\n')]
def ls(*options) pass
def mv(*options) pass
a = ls('*.txt')
b = forloop_pipe(a, mv, '/home')This is a very simple example with a lot of flaws, but it explains what I'm trying to get in bash command .
73 Answers
I'm not sure this is a straight answer to the question (there are much more than one questions), but I think you are asking for something like:
forloop with command substitution orwhileloop with process substitution.
for loop with $(command substitution)
Command substitution allows the output of a command to replace the command itself. We can combine this functionality with the for loop in this way:
for item in "$(find . -type f)"
do echo "$item" | tee -a ./"file-list.txt"
donewhile loop with <(process substitution)
Process substitution allows a process’s input or output to be referred to using a filename. We can combine this functionality with the while loop, by the help of the built in read, in this way:
while IFS= read -r item
do echo "$item" | tee -a ./"file-list.txt"
done < <(find . -type f)Within the final part < <(find . -type f), the first < means the stdin of the while loop and <(find . -type f) will be treated as a file. More about the construction IFS= read -r line you can read in this great post of Stéphane Chazelas.
You can achieve the same as the above by using pipe instead of process substitution (but this option is not preferred, because it could cause errors):
find . -type f | while IFS= read -r file
do echo "$file" | tee -a ./"file-list.txt"
donePipe the output of a loop to another command
Further we can pipe or redirect the output of the loop to another command or function:
while IFS= read -r -d '' item; do # Compose the name of the new file DIR="$(dirname "${item}")" FILE_NAME="$(basename "${item}")" NEW_FILE_NAME="new-${FILE_NAME}" # move the file and suppress the potential output # of the 'mv' command by redirecting it to a log file mv "${item}" "${DIR}/${NEW_FILE_NAME}" >>/tmp/mv-loop.log 2>&1 # output the name of the new file in order to be processed # by the next (piped) command printf '%b' "${DIR}/${NEW_FILE_NAME}"'\0'
done < <(find . -type f -print0) | xargs -0 -I{} 7z a -t7z "the_archive_name.7z" {}Note within the above example a null delimiters is used everywhere:
find . -print0,IFS= read -r -d '' item- reference,printf '%b' a'\0'b- reference,xargs -0(in this case we do not actually need-I{}and{}within thexargscommand).
This example will move (rename) all files recursively, but within the archive the directory structure wont appear.
1Is there a simple and common way to do
Answer: No, every command is different.
Regarding your questions:
You don't need
-t, but thenmvwill run for each file, which will be a lot slower. You need to end your-execwith\;instead of+.A pipe has nothing to do with a
forloop, a pipe sends stdout from left side of the pipe to stdin on the right side.xargsreads from stdin (or from a file with-aoption) and runs a command with arguments taken from the input.lshas no-execoption.lswill work withxargs, but not withxargs -0becauselshas no option for NULL delimited output. That is also why you should not uselswith pipes at all (file names are allowed to have newlines).For
mv,-tis target directory, while for7zit is archive type, why do you think it should work?
For me it seems you're using commands you do not really understand. This won't work for any programming or scripting language.
Btw: You should use xargs with -r option to avoid issues with empty input.
Maybe your answer is xargs -n 1
See this example: 1 only line (loop generated for gzip):
ls | grep -i '.csv' | xargs -n 1 gzipor
ls | grep -i '.gz' | xargs -n 1 gunzip