วิธีการทำให้ bash glob เป็นตัวแปรสตริง


14

ข้อมูลระบบ

ระบบปฏิบัติการ: OS X

bash: GNU bash, รุ่น 3.2.57 (1) - ปล่อย (x86_64-apple-darwin16)

พื้นหลัง

ฉันต้องการให้ไทม์แมชชีนแยกชุดไดเรกทอรีและไฟล์ออกจากโครงการ git / nodejs ทั้งหมดของฉัน ไดเรกทอรีโครงการของฉันอยู่ใน~/code/private/และดังนั้นฉันพยายามที่จะใช้ทุบตีวนลูปที่จะทำ~/code/public/tmutil

ปัญหา

เวอร์ชั่นสั้น

หากฉันมีตัวแปรสตริงที่คำนวณได้kฉันจะทำให้มันกลมกลืนไปหรือขวาก่อน for-loop ได้อย่างไร:

i='~/code/public/*'
j='*.launch'
k=$i/$j # $k='~/code/public/*/*.launch'

for i in $k # I need $k to glob here
do
    echo $i
done

k=$i/$jในรุ่นยาวด้านล่างนี้คุณจะเห็น ดังนั้นฉันไม่สามารถเข้ารหัสสตริงใน for for loop ได้

รุ่นยาว

#!/bin/bash
exclude='
*.launch
.classpath
.sass-cache
Thumbs.db
bower_components
build
connect.lock
coverage
dist
e2e/*.js
e2e/*.map
libpeerconnection.log
node_modules
npm-debug.log
testem.log
tmp
typings
'

dirs='
~/code/private/*
~/code/public/*
'

for i in $dirs
do
    for j in $exclude
    do
        k=$i/$j # It is correct up to this line

        for l in $k # I need it glob here
        do
            echo $l
        #   Command I want to execute
        #   tmutil addexclusion $l
        done
    done
done

เอาท์พุต

พวกเขาไม่ได้โค้ง ไม่ใช่สิ่งที่ฉันต้องการ

~/code/private/*/*.launch                                                                                   
~/code/private/*/.DS_Store                                                                                  
~/code/private/*/.classpath                                                                                 
~/code/private/*/.sass-cache                                                                                
~/code/private/*/.settings                                                                                  
~/code/private/*/Thumbs.db                                                                                  
~/code/private/*/bower_components                                                                           
~/code/private/*/build                                                                                      
~/code/private/*/connect.lock                                                                               
~/code/private/*/coverage                                                                                   
~/code/private/*/dist                                                                                       
~/code/private/*/e2e/*.js                                                                                   
~/code/private/*/e2e/*.map                                                                                  
~/code/private/*/libpeerconnection.log                                                                      
~/code/private/*/node_modules                                                                               
~/code/private/*/npm-debug.log                                                                              
~/code/private/*/testem.log                                                                                 
~/code/private/*/tmp                                                                                        
~/code/private/*/typings                                                                                    
~/code/public/*/*.launch                                                                                    
~/code/public/*/.DS_Store                                                                                   
~/code/public/*/.classpath                                                                                  
~/code/public/*/.sass-cache                                                                                 
~/code/public/*/.settings                                                                                   
~/code/public/*/Thumbs.db                                                                                   
~/code/public/*/bower_components                                                                            
~/code/public/*/build                                                                                       
~/code/public/*/connect.lock                                                                                
~/code/public/*/coverage                                                                                    
~/code/public/*/dist                                                                                        
~/code/public/*/e2e/*.js                                                                                    
~/code/public/*/e2e/*.map                                                                                   
~/code/public/*/libpeerconnection.log                                                                       
~/code/public/*/node_modules                                                                                
~/code/public/*/npm-debug.log                                                                               
~/code/public/*/testem.log                                                                                  
~/code/public/*/tmp                                                                                         
~/code/public/*/typings

เครื่องหมายคำพูดเดี่ยวหยุดการแก้ไขเชลล์ใน Bash ดังนั้นคุณอาจลองใส่เครื่องหมายคำพูดของคุณซ้ำ
โทมัส N

@ โทมัสไม่ไม่ทำงาน kเป็นสตริงที่คำนวณได้และฉันต้องการมันคงเป็นอย่างนั้นจนถึงลูป โปรดตรวจสอบเวอร์ชั่นยาวของฉัน
John Siu

@ThomasN ฉันอัปเดตเวอร์ชันสั้น ๆ เพื่อให้ชัดเจนยิ่งขึ้น
John Siu

คำตอบ:


18

คุณสามารถบังคับให้ประเมินผลอีกรอบด้วยevalแต่นั่นไม่จำเป็นจริงๆ (และevalเริ่มมีปัญหาร้ายแรงในขณะที่ชื่อไฟล์ของคุณมีอักขระพิเศษ$) ปัญหาไม่ได้เกิดจากการวนซ้ำ แต่ด้วยการขยายตัวของตัวหนอน

การเกิด Globbing เกิดขึ้นหลังจากการขยายตัวของตัวแปรหากตัวแปรไม่ได้ถูกอ้างอิงเช่นที่นี่(*) :

$ x="/tm*" ; echo $x
/tmp

ดังนั้นในหลอดเลือดดำเดียวกันนี้จะคล้ายกับสิ่งที่คุณทำและทำงาน:

$ mkdir -p ~/public/foo/ ; touch ~/public/foo/x.launch
$ i="$HOME/public/*"; j="*.launch"; k="$i/$j"
$ echo $k
/home/foo/public/foo/x.launch

แต่ด้วยเครื่องหมายตัวหนอนจะไม่:

$ i="~/public/*"; j="*.launch"; k="$i/$j"
$ echo $k
~/public/*/*.launch

นี่เป็นเอกสารที่ชัดเจนสำหรับ Bash:

คำสั่งของการขยายคือ: การขยายรั้ง; การขยายตัวตัวหนอนพารามิเตอร์และการขยายตัวตัวแปร ...

การขยายตัวของตัวหนอนเกิดขึ้นก่อนการขยายตัวของตัวแปรดังนั้นตัวหนอนภายในตัวแปรจะไม่ถูกขยาย วิธีแก้ปัญหาง่าย ๆ คือการใช้$HOMEหรือเส้นทางแบบเต็มแทน

(* การขยายความโกลว์จากตัวแปรมักไม่ใช่สิ่งที่คุณต้องการ)


อีกสิ่งหนึ่งที่:

เมื่อคุณวนซ้ำรูปแบบดังที่นี่:

exclude="foo *bar"
for j in $exclude ; do
    ...

โปรดทราบว่าในขณะที่$excludeไม่ได้กล่าวถึงมันทั้งสองแยกและกลมกลืนไปที่จุดนี้ ดังนั้นหากไดเรกทอรีปัจจุบันมีสิ่งที่ตรงกับรูปแบบมันจะขยายไปที่:

$ i="$HOME/public/foo"
$ exclude="*.launch"
$ touch $i/real.launch
$ for j in $exclude ; do           # split and glob, no match
    echo "$i"/$j ; done
/home/foo/public/foo/real.launch

$ touch ./hello.launch
$ for j in $exclude ; do           # split and glob, matches in current dir!
    echo "$i"/$j ; done
/home/foo/public/foo/hello.launch  # not the expected result

ในการหลีกเลี่ยงปัญหานี้ให้ใช้ตัวแปรอาเรย์แทนสตริงที่แบ่ง:

$ exclude=("*.launch")
$ exclude+=("something else")
$ for j in "${exclude[@]}" ; do echo "$i"/$j ; done
/home/foo/public/foo/real.launch
/home/foo/public/foo/something else

ในฐานะที่เป็นโบนัสเพิ่มเติมรายการอาร์เรย์สามารถมีช่องว่างโดยไม่มีปัญหาเกี่ยวกับการแยก


คุณสามารถทำสิ่งที่คล้ายกันได้find -pathหากคุณไม่คำนึงถึงระดับไดเรกทอรีไฟล์ที่ควรจะเป็น เช่นหาเส้นทางที่ลงท้ายด้วย/e2e/*.js:

$ dirs="$HOME/public $HOME/private"
$ pattern="*/e2e/*.js"
$ find $dirs -path "$pattern"
/home/foo/public/one/two/three/e2e/asdf.js

เราต้องใช้$HOMEแทน~ด้วยเหตุผลเดียวกับก่อนหน้านี้และ$dirsจำเป็นต้องถอนการอ้างอิงในfindบรรทัดคำสั่งเพื่อให้ได้รับการแยก แต่$patternควรจะยกมาเพื่อไม่ให้เชลล์ขยายโดยไม่ตั้งใจ

(ฉันคิดว่าคุณสามารถเล่นกับ-maxdepthGNU ในการค้นหาเพื่อ จำกัด การค้นหาที่ลึกลงไปถ้าคุณสนใจ แต่นั่นเป็นปัญหาที่แตกต่างออกไปเล็กน้อย)


คุณเป็นคำตอบเดียวfindใช่หรือไม่ ฉันกำลังสำรวจเส้นทางนั้นด้วยเช่นกันเนื่องจาก for-loop กำลังซับซ้อน แต่ฉันมีปัญหากับ '-path'
John Siu

ให้เครดิตกับคุณเนื่องจากข้อมูลของคุณเกี่ยวกับเครื่องหมายตัวหนอน '~' นั้นตรงไปที่ประเด็นหลัก ฉันจะโพสต์สคริปต์สุดท้ายและคำอธิบายในคำตอบอื่น แต่ก็ให้เครดิตเต็มที่กับคุณ: D
John Siu

@ JohnSiu ใช่การใช้ find เป็นสิ่งที่นึกถึงก่อน มันอาจใช้งานได้เช่นกันขึ้นอยู่กับความต้องการที่แน่นอน (หรือดีกว่าเช่นกันสำหรับการใช้งานบางอย่าง)
ilkkachu

1
@kevinarpe ฉันคิดว่าอาร์เรย์มีความหมายโดยทั่วไปสำหรับการทำเช่นนั้นและใช่"${array[@]}"(พร้อมด้วยเครื่องหมายคำพูด!) มีการบันทึกไว้ (ดูที่นี่และที่นี่ ) เพื่อขยายไปยังองค์ประกอบต่างๆ
ilkkachu

1
@sixtyfive [abc]ก็เป็นส่วนมาตรฐานของรูปแบบ globเช่น?ฉันไม่คิดว่ามันจะจำเป็นต้องครอบคลุมพวกเขาทั้งหมดที่นี่
ilkkachu

4

คุณสามารถบันทึกเป็นอาร์เรย์แทนที่จะเป็นสตริงเพื่อใช้ในภายหลังในหลายกรณีและปล่อยให้การวนรอบเกิดขึ้นเมื่อคุณกำหนด ในกรณีของคุณตัวอย่างเช่น:

k=(~/code/public/*/*.launch)
for i in "${k[@]}"; do

หรือในตัวอย่างต่อไปคุณจะต้องevalมีสตริง

dirs=(~/code/private/* ~/code/public/*)
for i in "${dirs[@]}"; do
    for j in $exclude; do
        eval "for k in $i/$j; do tmutil addexclusion \"\$k\"; done"
    done
done

1
โปรดทราบว่า$excludeมีอักขระตัวแทนคุณจะต้องปิดการใช้งาน globbing ก่อนที่จะใช้ตัวดำเนินการแยก + globในนั้นและเรียกคืนสำหรับ$i/$jและไม่ใช้evalแต่ใช้"$i"/$j
Stéphane Chazelas

ทั้งคุณและ ilkkachu ให้คำตอบที่ดี อย่างไรก็ตามคำตอบของเขาระบุปัญหา ดังนั้นเครดิตกับเขา
John Siu

2

@ilkkachu คำตอบแก้ไขปัญหาหลักของ globbing เครดิตทั้งหมดกับเขา

V1

อย่างไรก็ตามเนื่องจากexcludeมีรายการทั้งที่มีและไม่มี wildcard (*) และอาจไม่มีอยู่ทั้งหมดจึงจำเป็นต้องมีการตรวจสอบเพิ่มเติมหลังจากที่กลมกลืน$i/$jไป ฉันแบ่งปันสิ่งที่ค้นพบที่นี่

#!/bin/bash
exclude="
*.launch
.DS_Store
.classpath
.sass-cache
.settings
Thumbs.db
bower_components
build
connect.lock
coverage
dist
e2e/*.js
e2e/*.map
libpeerconnection.log
node_modules
npm-debug.log
testem.log
tmp
typings
"

dirs="
$HOME/code/private/*
$HOME/code/public/*
"

# loop $dirs
for i in $dirs; do
    for j in $exclude ; do
        for k in $i/$j; do
            echo -e "$k"
            if [ -f $k ] || [ -d $k ] ; then
                # Only execute command if dir/file exist
                echo -e "\t^^^ Above file/dir exist! ^^^"
            fi
        done
    done
done

เอาท์พุทอธิบาย

ต่อไปนี้เป็นผลลัพธ์บางส่วนเพื่ออธิบายสถานการณ์

/Volumes/HD2/JS/code/public/simple-api-example-ng2-express/a.launch
    ^^^ Above file/dir exist! ^^^
/Volumes/HD2/JS/code/public/simple-api-example-ng2-express/b.launch
    ^^^ Above file/dir exist! ^^^
/Volumes/HD2/JS/code/public/simple-api-example-ng2-express/.DS_Store
    ^^^ Above file/dir exist! ^^^

ข้างต้นเป็นตัวอธิบาย

/Volumes/HD2/JS/code/public/simple-api-example-ng2-express/.classpath
/Volumes/HD2/JS/code/public/simple-api-example-ng2-express/.sass-cache
/Volumes/HD2/JS/code/public/simple-api-example-ng2-express/.settings
/Volumes/HD2/JS/code/public/simple-api-example-ng2-express/Thumbs.db
/Volumes/HD2/JS/code/public/simple-api-example-ng2-express/bower_components
/Volumes/HD2/JS/code/public/simple-api-example-ng2-express/build
/Volumes/HD2/JS/code/public/simple-api-example-ng2-express/connect.lock
/Volumes/HD2/JS/code/public/simple-api-example-ng2-express/coverage
/Volumes/HD2/JS/code/public/simple-api-example-ng2-express/dist

รายการด้านบนแสดงขึ้นเนื่องจากรายการที่แยก ( $j) ไม่มีสัญลักษณ์แทนให้$i/$jเป็นการต่อสตริงธรรมดา อย่างไรก็ตามไม่มีไฟล์ / dir

/Volumes/HD2/JS/code/public/simple-api-example-ng2-express/e2e/*.js
/Volumes/HD2/JS/code/public/simple-api-example-ng2-express/e2e/*.map

ข้างต้นแสดงเป็นรายการที่แยก ( $j) มีสัญลักษณ์แทน แต่ไม่มีการจับคู่ไฟล์ / ไดเรกทอรีตรงที่$i/$jส่งกลับสตริงเดิม

V2

V2 ใช้เครื่องหมายคำพูดเดี่ยวevalและshopt -s nullglobเพื่อให้ได้ผลลัพธ์ที่สะอาด ไม่จำเป็นต้องมีการตรวจสอบไฟล์ / dir ขั้นสุดท้าย

#!/bin/bash
exclude='
*.launch
.sass-cache
Thumbs.db
bower_components
build
connect.lock
coverage
dist
e2e/*.js
e2e/*.map
libpeerconnection.log
node_modules
npm-debug.log
testem.log
tmp
typings
'

dirs='
$HOME/code/private/*
$HOME/code/public/*
'

for i in $dirs; do
    for j in $exclude ; do
        shopt -s nullglob
        eval "k=$i/$j"
        for l in $k; do
            echo $l
        done
        shopt -u nullglob
    done
done

ปัญหาหนึ่งก็คือว่าสิ่งที่อยู่ในfor j in $excludeนั้น$excludeสามารถขยายตัวได้ในช่วงเวลาของ$excludeการขยายตัวนั้น(และเรียกร้องevalสิ่งที่กำลังถามหาปัญหา) คุณต้องการเปิดใช้งานสำหรับ globbing for i in $dirและแต่ไม่ได้สำหรับfor l in $k for j in $excludeคุณต้องการset -fก่อนหน้าหลังและset +fอีกอัน โดยทั่วไปแล้วคุณต้องการปรับค่าตัวแยก + glob ของคุณก่อนใช้งาน ไม่ว่าในกรณีใดคุณไม่ต้องการให้มีการหาร + glob echo $lดังนั้น$lควรอ้างอิงที่นี่
Stéphane Chazelas

@ StéphaneChazelasคุณหมายถึง v1 หรือ v2 หรือไม่ สำหรับ v2 ทั้งสองexcludeและdirsอยู่ใน), so no globbing till เครื่องหมายคำพูดเดี่ยว ( eval`.
John Siu

globbing จะเกิดขึ้นเมื่อตัวแปร unquoted การขยายตัวในบริบทรายการที่ (ออกจากตัวแปร unquoted) คือสิ่งที่บางครั้งเราเรียกแยก + globผู้ประกอบการ ไม่มีการโค้งในการกำหนดตัวแปรสเกลาร์ foo=*และfoo='*'เหมือนกัน แต่echo $fooและecho "$foo"ไม่ใช่ (ในเชลล์เช่นbashนั้นจะได้รับการแก้ไขในเชลล์เช่น zsh, ปลาหรือ rc โปรดดูที่ลิงก์ด้านบน) ที่นี่คุณไม่ต้องการที่จะใช้ประกอบการนั้น แต่ในบางสถานที่เพียงส่วนหนึ่งแยกและอื่น ๆ เป็นเพียงส่วนหนึ่ง glob
Stéphane Chazelas

@ StéphaneChazelasขอบคุณสำหรับข้อมูล !!! เอาบางครั้งฉัน แต่ฉันเข้าใจความกังวลในขณะนี้ มันมีค่ามาก !! ขอบคุณ!!!
John Siu

1

ด้วยzsh:

exclude='
*.launch
.classpath
.sass-cache
Thumbs.db
...
'

dirs=(
~/code/private/*
~/code/public/*
)

for f ($^dirs/${^${=~exclude}}(N)) {
  echo $f
}

${^array}string$array[1]string $array[2]string...คือการขยายตัวตาม $=varคือการแยกคำในตัวแปร (บางเชลล์อื่นทำตามค่าเริ่มต้น!), $~varทำการวนรอบตัวแปร (บางเชลล์อื่น ๆ เช่นกันตามค่าเริ่มต้น (โดยทั่วไปเมื่อคุณไม่ต้องการให้เชลล์คุณต้องอ้าง$fไว้ข้างบนใน กระสุนอื่น ๆ ))

(N)เป็นตัวระบุแบบกลมที่เปิดใช้nullglobสำหรับแต่ละ globs ที่เกิดจาก$^array1/$^array2การขยายตัวนั้น นั่นทำให้ globs ขยายไปที่ไม่มีอะไรเมื่อพวกเขาไม่ตรงกัน นั่นก็เกิดขึ้นกับการเปลี่ยนรูปแบบที่ไม่ใช่รูปกลม~/code/private/foo/Thumbs.dbให้เป็นรูปแบบหนึ่งซึ่งหมายความว่าหากไม่มีสิ่งนั้นอยู่


นี่เป็นสิ่งที่ดีจริงๆ ฉันทดสอบและใช้งานได้ อย่างไรก็ตามดูเหมือนว่า zsh จะขึ้นบรรทัดใหม่เมื่อใช้เครื่องหมายคำพูดเดี่ยว วิธีการexcludeปิดล้อมมีผลต่อการส่งออก
John Siu

@ JohnSiu โอ้ใช่คุณพูดถูก ดูเหมือนว่าการแบ่ง + glob และสิ่งที่$^arrayต้องทำในสองขั้นตอนแยกต่างหากเพื่อให้แน่ใจว่าองค์ประกอบที่ว่างเปล่าถูกทิ้ง (ดูการแก้ไข) ที่ดูเหมือนบิตในข้อผิดพลาดzshฉันจะยกปัญหาในรายชื่อผู้รับจดหมายของพวกเขา
Stéphane Chazelas

ฉันมากับ v2 สำหรับทุบตีซึ่งสะอาดกว่า แต่ก็ยังไม่กะทัดรัดเท่ากับสคริปต์ zsh ของคุณ lol
John Siu
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.